123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270 |
- local modpath_default = minetest.get_modpath("default")
- if not (minetest.settings:get_bool("settlements_generate_books", true) and modpath_default) then
- return
- end
- -- internationalization boilerplate
- local S, NS = settlements.S, settlements.NS
- -- values taken from default's craftitems.lua
- local max_text_size = 10000
- local max_title_size = 80
- local short_title_size = 35
- local lpp = 14
- local generate_book = function(title, owner, text)
- local book = ItemStack("default:book_written")
- local meta = book:get_meta()
- meta:set_string("title", title:sub(1, max_title_size))
- meta:set_string("owner", owner)
- local short_title = title
- -- Don't bother trimming the title if the trailing dots would make it longer
- if #short_title > short_title_size + 3 then
- short_title = short_title:sub(1, short_title_size) .. "..."
- end
- meta:set_string("description", S("@1 by @2", short_title, owner))
- text = text:sub(1, max_text_size)
- text = text:gsub("\r\n", "\n"):gsub("\r", "\n")
- meta:set_string("text", text)
- meta:set_int("page", 1)
- meta:set_int("page_max", math.ceil((#text:gsub("[^\n]", "") + 1) / lpp))
- return book
- end
- ----------------------------------------------------------------------------------------------------------------
- local half_map_chunk_size = settlements.half_map_chunk_size
- local bookshelf_def = minetest.registered_items["default:bookshelf"]
- local bookshelf_on_construct
- if bookshelf_def then
- bookshelf_on_construct = bookshelf_def.on_construct
- end
- minetest.register_abm({
- label = "Settlement book authoring",
- nodenames = {"default:bookshelf"},
- interval = 60, -- Operation interval in seconds
- chance = 1440, -- Chance of triggering `action` per-node per-interval is 1.0 / this value
- catch_up = true,
- -- If true, catch-up behaviour is enabled: The `chance` value is
- -- temporarily reduced when returning to an area to simulate time lost
- -- by the area being unattended. Note that the `chance` value can often
- -- be reduced to 1.
- action = function(pos, node, active_object_count, active_object_count_wider)
- local inv = minetest.get_inventory( {type="node", pos=pos} )
- -- Can we fit a book?
- if not inv or not inv:room_for_item("books", "default:book_written") then
- return
- end
- -- find any settlements within the shelf's mapchunk
- -- There's probably only ever going to be one, but might as well do a closeness check to be on the safe side.
- local min_edge = vector.subtract(pos, half_map_chunk_size)
- local max_edge = vector.add(pos, half_map_chunk_size)
- local settlement_list = named_waypoints.get_waypoints_in_area("settlements", min_edge, max_edge)
- local closest_settlement
- for _, settlement in pairs(settlement_list) do
- local target_pos = settlement.pos
- if not closest_settlement or vector.distance(pos, target_pos) < vector.distance(pos, closest_settlement.pos) then
- closest_settlement = settlement
- end
- end
- if not closest_settlement then
- return
- end
- -- Get the settlement def and, if it generate books, generate one
- local data = closest_settlement.data
- local town_name = data.name
- local town_type = data.settlement_type
- local town_def = settlements.registered_settlements[town_type]
- if town_def and town_def.generate_book then
- local book = town_def.generate_book(closest_settlement.pos, town_name)
- if book then
- inv:add_item("books", book)
- if bookshelf_on_construct then
- bookshelf_on_construct(pos) -- this should safely update the bookshelf's infotext without disturbing its contents
- end
- end
- end
- end,
- })
- ---------------------------------------------------------------------------
- -- Commoditymarket ledgers
- if minetest.get_modpath("commoditymarket") then
- --{item=item, quantity=quantity, price=price, purchaser=purchaser, seller=seller, timestamp = minetest.get_gametime()}
- local log_to_string = function(log_entry, market)
- local anonymous = market.def.anonymous
- local purchaser = log_entry.purchaser
- local seller = log_entry.seller
- local purchaser_name
- if purchaser == seller then
- purchaser_name = S("themself")
- elseif anonymous then
- purchaser_name = S("someone")
- else
- purchaser_name = purchaser.name
- end
- local seller_name
- if anonymous then
- seller_name = S("someone")
- else
- seller_name = seller.name
- end
- local itemname = log_entry.item
- local item_def = minetest.registered_items[log_entry.item]
- if item_def then
- itemname = item_def.description:gsub("\n", " ")
- end
- return S("On day @1 @2 sold @3 @4 to @5 at @6@7 each for a total of @6@8.",
- math.ceil(log_entry.timestamp/86400), seller_name, log_entry.quantity, itemname,
- purchaser_name, market.def.currency_symbol, log_entry.price, log_entry.quantity*log_entry.price)
- end
- local get_log_strings = function(market, quantity)
- local accounts = market.player_accounts
- local all_logs = {}
- for player_name, account in pairs(accounts) do
- for _, log_entry in pairs(account.log) do
- table.insert(all_logs, log_entry)
- end
- end
- if #all_logs == 0 then
- return
- end
- table.sort(all_logs, function(log1, log2) return log1.timestamp > log2.timestamp end)
- local start_range = math.max(#all_logs, #all_logs - quantity)
- local start_point = math.random(start_range)
- local end_point = math.min(start_point+quantity, #all_logs)
- local out = {}
- local last_timestamp = all_logs[end_point].timestamp
- for i = start_point, end_point do
- table.insert(out, log_to_string(all_logs[i], market))
- end
- return out, last_timestamp
- end
- settlements.generate_ledger = function(market_name, town_name)
- local market = commoditymarket.registered_markets[market_name]
- if not market then
- return
- end
- local strings, last_timestamp = get_log_strings(market, math.random(5,15))
- if not strings then
- return
- end
- strings = table.concat(strings, "\n")
- local day = math.ceil(last_timestamp/86400)
- local title = S("@1 Ledger on Day @2", market.def.description, day)
- local author = S("@1's market clerk", town_name)
- return generate_book(title, author, strings)
- end
- end
- --------------------------------------------------------------------------------
- -- Travel guides
- -- returns {pos, data}
- local half_map_chunk_size = settlements.half_map_chunk_size
- local get_random_settlement_within_range = function(pos, range_max, range_min)
- range_min = range_min or half_map_chunk_size -- If no minimum provided, at least don't look within your own chunk
- if range_max <= range_min then
- return
- end
- local min_edge = vector.subtract(pos, range_max)
- local max_edge = vector.add(pos, range_max)
- local settlement_list = named_waypoints.get_waypoints_in_area("settlements", min_edge, max_edge)
- local settlements_within_range = {}
- for _, settlement in pairs(settlement_list) do
- local distance = vector.distance(pos, settlement.pos)
- if distance < range_max and distance > range_min then
- table.insert(settlements_within_range, settlement)
- end
- end
- if #settlements_within_range == 0 then
- return
- end
- local target = settlements_within_range[math.random(#settlements_within_range)]
- return target
- end
- local compass_dirs = {
- [0] = S("west"),
- S("west-southwest"),
- S("southwest"),
- S("south-southwest"),
- S("south"),
- S("south-southeast"),
- S("southeast"),
- S("east-southeast"),
- S("east"),
- S("east-northeast"),
- S("northeast"),
- S("north-northeast"),
- S("north"),
- S("north-northwest"),
- S("northwest"),
- S("west-northwest"),
- S("west"),
- }
- local increment = 2*math.pi/#compass_dirs -- Divide the circle up into pieces
- local reframe = math.pi - increment/2 -- Adjust the angle to run through a range divisible into indexes
- local compass_direction = function(p1, p2)
- local dir = vector.subtract(p2, p1)
- local angle = math.atan2(dir.z, dir.x);
- angle = angle + reframe
- angle = math.ceil(angle / increment)
- return compass_dirs[angle]
- end
- local get_altitude = function(pos)
- if pos.y > 100 then
- return S("highlands")
- elseif pos.y > 20 then
- return S("midlands")
- elseif pos.y > 0 then
- return S("lowlands")
- else
- -- TODO: need to update this system once there are underground settlements
- return S("waters")
- end
- end
- settlements.generate_travel_guide = function(source_pos, source_name)
- local range = math.random(settlements.min_dist_settlements*2, settlements.min_dist_settlements*5)
- local target = get_random_settlement_within_range(source_pos, range)
- if not target then
- return
- end
- local target_name = target.data.name
- local target_type = target.data.settlement_type
- local target_desc = S("settlement")
- if target_type then
- local def = settlements.registered_settlements[target_type]
- if def and def.description then
- target_desc = def.description
- end
- end
- local title = S("A travel guide to @1", target_name)
- local author = S("a resident of @1", source_name)
- local dir = compass_direction(source_pos, target.pos)
- local distance = vector.distance(source_pos, target.pos)
- local kilometers = string.format("%.1f", distance/1000)
- local altitude = get_altitude(target.pos)
- local text = S("In the @1 @2 kilometers to the @3 of @4 lies the @5 of @6.", altitude, kilometers, dir, source_name, target_desc, target_name)
- return generate_book(title, author, text)
- end
|