init.lua 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122
  1. if not minetest.global_exists("survivalist") then survivalist = {} end
  2. survivalist.modpath = minetest.get_modpath("survivalist")
  3. survivalist.players = survivalist.players or {}
  4. survivalist.groups = survivalist.groups or {}
  5. -- XP requirements. Amounts are somewhat arbitrary.
  6. survivalist.xp_minimum = 700
  7. survivalist.xp_surface = 700
  8. survivalist.xp_cave = 1500
  9. survivalist.xp_nether = 2400
  10. -- XP completion requirements. Amounts are somewhat arbitrary.
  11. survivalist.completion_xp_surface = 1100
  12. survivalist.completion_xp_cave = 2700
  13. survivalist.completion_xp_nether = 4500
  14. function survivalist.have_xp(pname, ch)
  15. local amount = (survivalist["xp_" .. ch]) or 0
  16. return (xp.get_xp(pname, "digxp") >= amount)
  17. end
  18. -- Localize for performance.
  19. local vector_distance = vector.distance
  20. local vector_round = vector.round
  21. local math_floor = math.floor
  22. local math_random = math.random
  23. -- Positions of surface & nether cities.
  24. local surfacecitypos = {x=0, y=-8, z=0}
  25. local nethercitypos = {x=0, y=-30793, z=0}
  26. -- Valid gamemodes are 'surface', 'cave', & 'nether'.
  27. -- The game rules description. Shown in formspec.
  28. survivalist.gamerules =
  29. "===| Survival Challenge Description |===\n\n" ..
  30. "This page contains the rules for this mini-game, with options to begin a Challenge or claim victory on a Challenge currently in-progress.\n\n" ..
  31. "The Survival Challenge focuses on survival in the wild far from developed regions. You can play it solo or with other players.\n\n" ..
  32. "You will need to scroll this text in order to read all of it.\n\n" ..
  33. "When beginning a Challenge it is possible to end up in a starting situation where survival is impossible. You always have the option to cancel without affecting your score.\n\n" ..
  34. "On accepting a challenge you are transported into a small dungeon, which is at a distant location in the world very far from the Surface Colony and the Nether City. The minimum distance you’ll be teleported is 10,000 meters; the server will not choose a dungeon closer to either city than this. You’ll have the basic starting items that every player starts with when they first join the server, and the dungeon has a chest containing additional items to make getting a head-start a little easier, since otherwise some scenarios would be impossible to beat. The chest contents differ between challenge types and are somewhat random.\n\n" ..
  35. "Before you can begin a challenge all your inventories must be empty: your main inventory, your craft grid, the craft-preview and craft-output inventory slots (yes, those too, cheaters!), your Star-Fissure Box, your armor inventory, your bags, and the wielditem inventory (if you don’t know what this is, then don’t worry about it). Proof of Citizenship tokens are exempt.\n\n" ..
  36. "You can play this mini-game cooperatively. To do so one player should to start a Survival Challenge just as if they were going to play solo. Whenever a player starts a Challenge there is a 5 minute window in which other players can join the first player by being transported to the same dungeon. To join another player in their dungeon, you must stand within 5 meters of the place where they stood at the time they began their Challenge. You must also choose the same gamemode (Iceworld, Underearth, or Netherealm) as the first player. If you are too far away, or you choose a wrong gamemode, or the 5 minute window has passed, you won’t be transported to the same dungeon. If you are are unsuccessful and are not teleported to the same dungeon, you can simply cancel and try again if you wish. There is no limit on the number of players who may play cooperatively. Every player who joins extends the 5-minute window a bit. Do note that supplies are not increased with each player beyond the first, so you’ll need to share. Players win or lose the Challenge separately, so if one player has a fatal accident, the remaining players can keep playing.\n\n" ..
  37. "Finding a suitable location to build the starting dungeon can take the server some time, so you have a few seconds to interact with the world after accepting a challenge. Don’t rely on this, though.\n\n" ..
  38. "(Note to builders in distant lands: the server will not overwrite any of your builds when creating a player’s starting dungeon, as long as your builds are protected. Although the world is vast and the chance of randomly selecting a location inside someone else’s home is small, the server nonetheless performs checks to avoid accidents.)\n\n" ..
  39. "In order to win after starting a challenge, you must find your way back to the Surface Colony OR the Nether City and claim victory (press the ‘Claim Victory’ button below these rules) in the Central Square of either location. Finding civilization to claim victory is your goal; avoiding a fatal death during your travels in the wild is your challenge. Rules for death are below the Challenge descriptions.\n\n" ..
  40. "Note that you may to use your Proof of Citizenship to teleport you the rest of the way to the Surface Colony or Nether City once you are in range of the teleport beacons. Use of diamond teleporters is also allowed, as are flameportals which go to and from the Netherealm, or any other form of transportation. However, when starting a challenge your flameportal’s previous return position (if you had one set) is erased to prevent using a return portal as a ‘cheat’ to obtain an easy win. The same is true for beds.\n\n" ..
  41. "There are three challenge types: the Iceworld Challenge, the Underearth Challenge, and the Netherealm Challenge, with difficulty being in that order. Each challenge awards Skill Marks when victory is successfully claimed. The Skill Marks are Copper, Silver, and Gold. The amount of marks you’ll receive on successful completion of a challenge depends on the distance you travel to either the Surface Colony or the Nether City, starting from your initial dungeon. You receive 1 mark for every 1K meters of distance (rounded down), minus the first 10 kilometers. You always receive at least 1 Skill Mark.\n\n" ..
  42. "The following is a basic description of each challenge.\n\n" ..
  43. "In the Iceworld Challenge you start in an ice-and-brick dungeon at what is normally sea-level (if this server were to have a liquid sea). Chances are, you’ll be under a mountain or below a raised plain, so your first goal will be to dig up and find the surface. If you are very lucky you will be on an ice-lake instead. Avoid the icemen and don’t go out at night! This is the easiest challenge. The first time you complete it successfully, the skill learned causes your wield-hotbar (the 8 itemslots below your health and hunger stats) to expand to 16 slots for easier building. (Note: if you prefer the regular 8 slots anyway and don’t want the bigger version, you can let the server administrator know, to get it reset back to 8 slots!)\n\n" ..
  44. "In the Underearth Challenge you start in a cobble dungeon deep under the earth, between -24K and -3K. Since you are underground, your first priority is using the materials in the starting chest to build a small farm. Note that the server never locates a dungeon to be floating in the middle of a caverealm void, but it is remotely possible that you could have lava outside the dungeon walls. Once you have a food source, the main difficulty is digging your way to the surface or to the nether (if you choose to claim victory in the Nether City). Beware of mining instabilities! Note that this Challenge typically takes the longest to complete. Once you’ve established a base with a bed, consider using Obsidian Gateways to try and bring yourself closer to the surface. The first time you complete this Challenge, you unlock a hidden feature of the Key of Citizenship which allows you to jaunt (teleport) to other players who also possess a Key—provided their Key’s beacon signal can be successfully triangulated ….\n\n" ..
  45. "The Netherealm Challenge is similar to the Underearth Challenge, except that your starting dungeon is located in the second-worst place in the world, some 60 meters under the brimstone ocean. (The worst place in the world is the islands directly above all that lava.) Your starting dungeon is made of black rackstone, but don’t imagine you’ll be able to use it for anything except stone picks. You need to be very, very careful in this challenge, because lava is everywhere, and netherack is nasty to dig. There are very few usable resources in the nether, so finding the few resources you can is very important. The first thing you need to do is dig up to the islands without letting the brimstone ocean flow into your dungeon. Once there you can explore the brimstone cavern for a few meager resources. In this Challenge, you are recommended to claim victory in the Nether City rather than attempt to dig to the surface. However, those who dig to the surface will receive more reward. If you choose to dig up out of the nether into normal rock you’ll be able to find a few more resources, and the caverealms of the Underearth Challenge will seem like a garden. :-) The first time you beat this Challenge, you unlock a hidden feature of the Key of Citizenship which allows you to access its little-understood cloaking capabilities, which can render you invisible in all but a few circumstances ….\n\n" ..
  46. "After starting a Challenge you may cancel it without affecting your death/success score. This option is available as long as a Challenge is running. The conditions for canceling a challenge are the same as the conditions for starting one: all your inventories must be empty except for passport tokens. (You cannot bring anything with you when you cancel.) You may choose this option if, for instance, you do not like your starting situation. When you cancel a Challenge you are returned to the same spot you were when you began the Challenge.\n\n" ..
  47. "Rules for death: you lose the Challenge if you die and have not slept in a bed. Lacking a bed is your initial state whenever you begin a Challenge, and you avoid losing the Challenge by crafting a bed and sleeping in it. Afterwards if you die you will respawn in your bed and the Challenge will continue as before. Note that a bed is only good for a limited number of respawns, so you should remember to sleep often to refresh it.\n\n" ..
  48. "Warning: since your bed is lost if you are killed by another player (but not by a mob or the environment), the effect of being murdered is that you will fail the Challenge. Your murderer does not get any special benefits other than hearing your screams of fury.\n\n" ..
  49. "Each player’s total number of victories, fatal deaths, and victory streaks are recorded permanently, as well as the number of victories in the three individual challenges, and the total number of marks earned. This information is never reset.\n\n" ..
  50. "===| Challenge Description End |==="
  51. -- Add player to a list of players contained in the database.
  52. -- This is because we can't iterate over the database entries directly,
  53. -- so we need a way to find all players who have ranking entries.
  54. function survivalist.add_player_to_rankings(pname)
  55. -- Obtain the database in string form.
  56. local data = survivalist.modstorage:get_string(":ranked_players:")
  57. -- If the string is empty, create a dummy table for deserialization.
  58. if data == "" then
  59. data = "return {}"
  60. end
  61. -- Deserialize the rankings database into a table.
  62. local tb = minetest.deserialize(data)
  63. -- Add player to table.
  64. tb[pname] = true
  65. -- Reserialize the database table.
  66. local serialized = minetest.serialize(tb)
  67. -- Don't overwrite the old database unless the table serialized successfully.
  68. if type(serialized) == "string" then
  69. survivalist.modstorage:set_string(":ranked_players:", serialized)
  70. end
  71. end
  72. function survivalist.get_ranking_entries()
  73. -- Obtain the database in string form.
  74. local data = survivalist.modstorage:get_string(":ranked_players:")
  75. -- If the string is empty, create a dummy table for deserialization.
  76. if data == "" then
  77. data = "return {}"
  78. end
  79. -- Deserialize the rankings database into a table.
  80. local tb = minetest.deserialize(data)
  81. -- Return the table.
  82. return tb or {}
  83. end
  84. function survivalist.inventory_empty(inv, name)
  85. local count = 0
  86. for i = 1, inv:get_size(name) do
  87. local stack = inv:get_stack(name, i)
  88. if not passport.is_passport(stack:get_name()) then
  89. if stack:get_count() > 0 then
  90. count = count + stack:get_count()
  91. end
  92. end
  93. end
  94. return (count == 0)
  95. end
  96. function survivalist.check_inventories_empty(pname)
  97. local player = minetest.get_player_by_name(pname)
  98. if not player then
  99. return
  100. end
  101. local inv = player:get_inventory()
  102. if survivalist.inventory_empty(inv, "main") and
  103. survivalist.inventory_empty(inv, "craft") and
  104. survivalist.inventory_empty(inv, "armor") and
  105. survivalist.inventory_empty(inv, "voidchest:voidchest") and
  106. survivalist.inventory_empty(inv, "craftresult") and
  107. survivalist.inventory_empty(inv, "craftpreview") and
  108. survivalist.inventory_empty(inv, "bag1contents") and
  109. survivalist.inventory_empty(inv, "bag2contents") and
  110. survivalist.inventory_empty(inv, "bag3contents") and
  111. survivalist.inventory_empty(inv, "bag4contents") and
  112. survivalist.inventory_empty(inv, "xchest") and
  113. survivalist.inventory_empty(inv, "bag1") and
  114. survivalist.inventory_empty(inv, "bag2") and
  115. survivalist.inventory_empty(inv, "bag3") and
  116. survivalist.inventory_empty(inv, "bag4") then
  117. return true
  118. end
  119. end
  120. -- Loot parameters & stuff.
  121. dofile(survivalist.modpath .. "/loot.lua")
  122. -- Actually teleport the player to the start location and announce in chat.
  123. -- Also, record that the game has begun.
  124. function survivalist.teleport_and_announce(pname, pos, gamemode)
  125. local player = minetest.get_player_by_name(pname)
  126. if not player then
  127. return
  128. end
  129. -- Player's inventories must be empty.
  130. if not survivalist.check_inventories_empty(pname) then
  131. minetest.chat_send_player(pname, "# Server: All your inventories (including the Starfissure Box and armor) must be empty before you can begin a challenge (the Proof of Citizenship does not count). You will receive starting items when you begin.")
  132. easyvend.sound_error(pname)
  133. return
  134. end
  135. -- Abort if player is trying to cheat by sitting in a cart. >:)
  136. if default.player_attached[pname] then
  137. minetest.chat_send_player(pname, "# Server: Transport error. Player attached!")
  138. return
  139. end
  140. -- Record home position.
  141. local homepos = vector_round(player:get_pos())
  142. -- Teleport player.
  143. wield3d.on_teleport()
  144. player:set_pos(vector.add(pos, {x=math_random(-3, 3), y=0.5, z=math_random(-3, 3)}))
  145. -- Make sure player is healthy.
  146. heal.heal_health_and_hunger(pname)
  147. -- Remove posibility of cheating via netherportals.
  148. flameportal.clear_return_location(pname)
  149. -- Clear bed respawn position. Player must make new bed to survive.
  150. local old_bed_pos = beds.get_respawn_pos_or_nil(pname)
  151. if old_bed_pos then
  152. local pmeta = player:get_meta()
  153. pmeta:set_string("survivalist_old_bed_pos", minetest.pos_to_string(old_bed_pos))
  154. end
  155. beds.clear_player_spawn(pname)
  156. -- Give the game name some interesting names.
  157. local gamestring = "Void"
  158. if gamemode == "surface" then
  159. gamestring = "Iceworld"
  160. elseif gamemode == "cave" then
  161. gamestring = "Underearth"
  162. elseif gamemode == "nether" then
  163. gamestring = "Netherealm"
  164. end
  165. -- Inform player the game has begun.
  166. if not gdac.player_is_admin(pname) then
  167. local dname = rename.gpn(pname)
  168. minetest.chat_send_all("# Server: Player <" .. dname .. "> has begun a test of skill in the " .. gamestring .. " at " .. rc.pos_to_namestr(vector_round(pos)) .. "!")
  169. else
  170. minetest.chat_send_player(pname, "# Server: You have begun a test of skill in the " .. gamestring .. " at " .. rc.pos_to_namestr(vector_round(pos)) .. "!")
  171. end
  172. survivalist.shout_player_stats(pname)
  173. minetest.chat_send_player(pname, "# Server: To win, you must find the city and claim victory in the Main Square. If you die without sleeping, you will fail the Challenge.")
  174. beds.report_respawn_status(pname)
  175. -- Give player the starting items.
  176. give_initial_stuff.give(player)
  177. -- Record the player's new gamemode.
  178. survivalist.modstorage:set_string(pname .. ":mode", gamemode)
  179. -- Record the player's starting position.
  180. survivalist.modstorage:set_string(pname .. ":pos", minetest.pos_to_string(vector_round(pos)))
  181. -- Record the player's home position. Used when canceling a Challenge.
  182. survivalist.modstorage:set_string(pname .. ":home", minetest.pos_to_string(homepos))
  183. -- Record the amount of XP player has at the start of the Challenge.
  184. survivalist.modstorage:set_string(pname .. ":xp", tostring(xp.get_xp(pname, "digxp")))
  185. -- Record that this player is accepting groups.
  186. survivalist.groups[pname] = survivalist.groups[pname] or {count=0}
  187. survivalist.groups[pname].count = survivalist.groups[pname].count + 1
  188. minetest.after(60*5, function()
  189. if survivalist.groups[pname] then
  190. survivalist.groups[pname].count = survivalist.groups[pname].count - 1
  191. if survivalist.groups[pname].count <= 0 then
  192. survivalist.groups[pname] = nil
  193. end
  194. end
  195. end)
  196. end
  197. -- This function must find a location for the player and teleport them there.
  198. -- Also create a starting dungeon (must not destroy protected stuff) and a chest with extra resources.
  199. function survivalist.prepare_dungeon(pname, pos, gamemode)
  200. -- Positions to load. Need a larger area in order to make sure any protections are discovered.
  201. local minp = vector.add(pos, vector.new(-64, -64, -64))
  202. local maxp = vector.add(pos, vector.new(64, 64, 64))
  203. -- Dungeon coordinates.
  204. local dminp = vector.add(pos, vector.new(-4, 0, -4))
  205. local dmaxp = vector.add(pos, vector.new(4, 4, 4))
  206. -- Copy the position table so it doesn't get modified.
  207. local pos2 = table.copy(pos)
  208. -- Build callback function. When the map is loaded, we need to check protections and create the dungeon.
  209. local tbparam = {}
  210. local cb = function(blockpos, action, calls_remaining, param)
  211. if action == core.EMERGE_CANCELLED or action == core.EMERGE_ERRORED then
  212. minetest.chat_send_player(pname, "# Server: Internal error, try again or report.")
  213. easyvend.sound_error(pname)
  214. return
  215. end
  216. -- We don't do anything until the last callback.
  217. if calls_remaining ~= 0 then
  218. return
  219. end
  220. -- Check for the presence of a city-block.
  221. if city_block:in_city_suburbs(pos2) then
  222. minetest.chat_send_player(pname, "# Server: Error: did not succeed in finding a suitable start location! If this happens, just try again.")
  223. easyvend.sound_error(pname)
  224. return
  225. end
  226. -- Check for protections, and if there are none, create a dungeon.
  227. for x = dminp.x, dmaxp.x, 1 do
  228. for y = dminp.y, dmaxp.y, 1 do
  229. for z = dminp.z, dmaxp.z, 1 do
  230. if minetest.test_protection({x=x, y=y, z=z}, "") then
  231. -- Return failure if target is protected. This shouldn't happen often.
  232. minetest.chat_send_player(pname, "# Server: Error: did not succeed in finding a suitable start location! If this happens, just try again.")
  233. easyvend.sound_error(pname)
  234. return
  235. end
  236. end
  237. end
  238. end
  239. -- Check if spawning in air (e.g., due to large cavern).
  240. if minetest.get_node(pos2).name == "air" then
  241. minetest.chat_send_player(pname, "# Server: Error: did not succeed in finding a suitable start location! If this happens, just try again.")
  242. easyvend.sound_error(pname)
  243. return
  244. end
  245. -- No protections? Create the dungeon.
  246. -- Generate dungeon for the player to spawn in.
  247. local path = survivalist.modpath .. "/schematics/survivalist_" .. gamemode .. "_dungeon.mts"
  248. minetest.place_schematic(vector.add(pos2, {x=-4, y=0, z=-4}), path, "random", nil, true)
  249. -- Choose a location for the chest.
  250. local chestpos = vector.add(pos2, vector.new(math_random(-3, 3), 1, math_random(-3, 3)))
  251. chestpos = vector_round(chestpos)
  252. -- Create chest with stuff.
  253. minetest.set_node(chestpos, {
  254. name = "morechests:goldchest_public_closed",
  255. param2 = math_random(0, 3),
  256. })
  257. local meta = minetest.get_meta(chestpos)
  258. local inv = meta:get_inventory()
  259. if inv then
  260. survivalist.fill_loot_chest(inv, gamemode)
  261. end
  262. -- Teleport player and announce.
  263. survivalist.teleport_and_announce(pname, pos2, gamemode)
  264. end
  265. -- Emerge the target area. Once emergence is complete the dungeon will spawn.
  266. minetest.chat_send_player(pname, "# Server: Checking reliability of target location ... please stand by, this can take several seconds.")
  267. minetest.emerge_area(minp, maxp, cb, tbparam)
  268. end
  269. function survivalist.shout_player_stats(pname)
  270. local ms = survivalist.modstorage
  271. local wins_total = pname .. ":wins_total"
  272. local wins_streak = pname .. ":wins_streak"
  273. local wins_surface = pname .. ":wins_surface"
  274. local wins_cave = pname .. ":wins_cave"
  275. local wins_nether = pname .. ":wins_nether"
  276. local wins_fail = pname .. ":wins_fail"
  277. local wins_bstreak = pname .. ":wins_bstreak"
  278. local wins_tokens = pname .. ":wins_tokens"
  279. if not gdac.player_is_admin(pname) then
  280. local dname = rename.gpn(pname)
  281. minetest.chat_send_all("# Server: Survivalist stats for <" ..
  282. dname .. ">: Victories: " .. ms:get_int(wins_total) ..
  283. ". Deaths: " .. ms:get_int(wins_fail) ..
  284. ". Iceworld: " .. ms:get_int(wins_surface) ..
  285. ". Underearth: " .. ms:get_int(wins_cave) ..
  286. ". Netherealm: " .. ms:get_int(wins_nether) ..
  287. ". C-Streak: " .. ms:get_int(wins_streak) ..
  288. ". B-Streak: " .. ms:get_int(wins_bstreak) ..
  289. ". Marks: " .. ms:get_int(wins_tokens) ..
  290. ".")
  291. end
  292. end
  293. function survivalist.game_in_progress(pname)
  294. local cg = survivalist.modstorage:get_string(pname .. ":mode")
  295. if cg == "surface" or cg == "cave" or cg == "nether" then
  296. return true
  297. end
  298. end
  299. -- This function is called when the player wants to begin the game.
  300. -- It must handle all game validation.
  301. function survivalist.start_game(pname, gamemode)
  302. -- Get player and make sure he exists.
  303. local player = minetest.get_player_by_name(pname)
  304. if not player then
  305. return
  306. end
  307. -- Is a game already running?
  308. local currentgame = survivalist.modstorage:get_string(pname .. ":mode")
  309. if currentgame == "surface" or currentgame == "cave" or currentgame == "nether" then
  310. minetest.chat_send_player(pname, "# Server: You are already engaged in a Survivalist Challenge, you cannot start a concurrent game.")
  311. easyvend.sound_error(pname)
  312. return
  313. end
  314. local pp = player:get_pos()
  315. if pp.y < -100 or pp.y > 1000 then
  316. minetest.chat_send_player(pname, "# Server: You need to be on the surface of the Overworld to start a challenge.")
  317. easyvend.sound_error(pname)
  318. return
  319. end
  320. -- Validate the gamemode.
  321. if type(gamemode) ~= "string" then
  322. minetest.chat_send_player(pname, "# Server: No starting option selected!")
  323. easyvend.sound_error(pname)
  324. return
  325. end
  326. if gamemode ~= "surface" and gamemode ~= "cave" and gamemode ~= "nether" then
  327. minetest.chat_send_player(pname, "# Server: You must choose a valid starting option!")
  328. easyvend.sound_error(pname)
  329. return
  330. end
  331. -- Player must have minimum XP for this kind of Challenge.
  332. if not survivalist.have_xp(pname, gamemode) then
  333. minetest.chat_send_player(pname, "# Server: Insufficient XP to start this Challenge.")
  334. easyvend.sound_error(pname)
  335. return
  336. end
  337. -- Player's inventories must be empty.
  338. if not survivalist.check_inventories_empty(pname) then
  339. minetest.chat_send_player(pname, "# Server: All your inventories (including the Starfissure Box and armor) must be empty before you can begin a challenge (the Proof of Citizenship does not count). You will receive starting items when you begin.")
  340. easyvend.sound_error(pname)
  341. return
  342. end
  343. local pos = {x=0, y=0, z=0}
  344. local group = false
  345. -- Group survival: check if the player should be grouped with someone else.
  346. local players = minetest.get_connected_players()
  347. for k, v in ipairs(players) do
  348. local oname = v:get_player_name()
  349. local omode = survivalist.modstorage:get_string(oname .. ":mode")
  350. -- Gamemodes must match.
  351. if omode == gamemode then
  352. local opos = minetest.string_to_pos(survivalist.modstorage:get_string(oname .. ":home"))
  353. -- Home position must have been recorded.
  354. if opos then
  355. -- We must be close enough to the other player's home pos, in order to be grouped with them.
  356. if vector_distance(opos, player:get_pos()) <= 5 then
  357. -- The other player must currently be accepting groups.
  358. -- Groups are not saved across restarts; this means that a player cannot
  359. -- team with another after the server restarts, even if less time than
  360. -- the time limit has gone by.
  361. if survivalist.groups[oname] then
  362. -- Get location of the other player's dungeon.
  363. local p2 = minetest.string_to_pos(survivalist.modstorage:get_string(oname .. ":pos"))
  364. if p2 then
  365. pos = p2
  366. group = true
  367. break
  368. end
  369. end
  370. end
  371. end
  372. end
  373. end
  374. if not group then
  375. -- Find a random position on the X,Z plane.
  376. while vector_distance(pos, surfacecitypos) < 10000 or vector_distance(pos, nethercitypos) < 10000 do
  377. for j, k in ipairs({"x", "z"}) do
  378. pos[k] = math_random(-30000, 30000)
  379. end
  380. -- Gamemode determines depth.
  381. if gamemode == "surface" then
  382. pos.y = -10
  383. elseif gamemode == "cave" then
  384. pos.y = math_random(-24000, -3000)
  385. elseif gamemode == "nether" then
  386. pos.y = math_random(-30860, -30810)
  387. end
  388. end
  389. end
  390. -- Prepare target location. The remaining logic is executed from a callback.
  391. if group then
  392. -- The dungeon has already been generated.
  393. survivalist.teleport_and_announce(pname, pos, gamemode)
  394. else
  395. -- Need to generate a dungeon, then teleport the player.
  396. survivalist.prepare_dungeon(pname, pos, gamemode)
  397. end
  398. end
  399. -- Checks if a player is in range to claim victory.
  400. -- Performs no other validation; this is a simple distance check.
  401. function survivalist.player_in_victory_range(pname)
  402. local player = minetest.get_player_by_name(pname)
  403. if not player then
  404. return
  405. end
  406. local pos = player:get_pos()
  407. if vector_distance(pos, surfacecitypos) <= 20 then
  408. return true
  409. elseif vector_distance(pos, nethercitypos) <= 20 then
  410. return true
  411. end
  412. end
  413. -- Called when the player wants to claim victory. Must validate.
  414. function survivalist.attempt_claim(pname)
  415. local player = minetest.get_player_by_name(pname)
  416. if not player then
  417. return
  418. end
  419. -- Make sure a game is actually running.
  420. local currentgame = survivalist.modstorage:get_string(pname .. ":mode")
  421. if currentgame ~= "surface" and currentgame ~= "cave" and currentgame ~= "nether" then
  422. minetest.chat_send_player(pname, "# Server: You are not currently engaged in a Survivalist Challenge.")
  423. easyvend.sound_error(pname)
  424. return
  425. end
  426. -- Check if player has earned the needed XP over the course of the Challenge.
  427. -- This ensures player does not complete the Challenge too fast, and needs to actually
  428. -- take some time, make a base/home, do some farming, etc!
  429. do
  430. local sxp = survivalist.modstorage:get_string(pname .. ":xp")
  431. local oxp = math.floor(tonumber(sxp) or 0)
  432. local cxp = math.floor(xp.get_xp(pname, "digxp"))
  433. local wxp = (survivalist["completion_xp_" .. currentgame]) or 0
  434. local dxp = (cxp - oxp)
  435. if dxp < wxp then
  436. local nxp = math.floor(oxp + wxp)
  437. minetest.chat_send_player(pname, "# Server: You need to obtain at least " .. wxp .. " XP over the course of the Challenge, first.")
  438. minetest.chat_send_player(pname, "# Server: You started with " .. oxp .. " XP. You must reach " .. nxp .. " XP!")
  439. easyvend.sound_error(pname)
  440. return
  441. end
  442. end
  443. -- Check if the player is in the city.
  444. local pos = player:get_pos()
  445. local cityname = ""
  446. local home_pos = minetest.string_to_pos(survivalist.modstorage:get_string(pname .. ":home"))
  447. -- The position and name of the city the player claims victory in.
  448. local finalcitypos
  449. if vector_distance(pos, surfacecitypos) <= 20 then
  450. finalcitypos = table.copy(surfacecitypos)
  451. cityname = "Surface Colony"
  452. elseif vector_distance(pos, nethercitypos) <= 20 then
  453. finalcitypos = table.copy(nethercitypos)
  454. cityname = "Nether City"
  455. elseif vector_distance(pos, home_pos) <= 20 then
  456. finalcitypos = table.copy(home_pos)
  457. cityname = "place they started"
  458. end
  459. if not finalcitypos then
  460. minetest.chat_send_player(pname, "# Server: You must be within 20 meters of the main square of the Surface Colony or the Nether City or the place where you started the challenge in order to claim victory!")
  461. easyvend.sound_error(pname)
  462. return
  463. end
  464. -- What rank (copper, silver, gold) has the player earned?
  465. local ranks = {
  466. ["surface"] = {rank="copper", upper="Copper"},
  467. ["cave"] = {rank="silver", upper="Silver"},
  468. ["nether"] = {rank="gold", upper="Gold"},
  469. }
  470. local rank = ranks[currentgame].rank
  471. local upperank = ranks[currentgame].upper
  472. -- Reward the player.
  473. local tokencount = 1
  474. local startpos = minetest.string_to_pos(survivalist.modstorage:get_string(pname .. ":pos"))
  475. -- If the starting position couldn't be parsed we'll just give the player 1 token.
  476. if startpos then
  477. local dist = vector_distance(finalcitypos, startpos)
  478. -- Discount the minimum distance.
  479. dist = dist - 10000
  480. -- Get distance in kilometers.
  481. -- One skill mark per extra kilometer over 10k.
  482. dist = math_floor(dist / 1000)
  483. -- Clamp, just in case.
  484. if dist < 1 then
  485. dist = 1
  486. end
  487. tokencount = dist
  488. end
  489. local dname = rename.gpn(pname)
  490. if not gdac.player_is_admin(pname) then
  491. minetest.chat_send_all("# Server: Player <" .. dname .. "> has claimed victory in the " .. cityname .. "!")
  492. minetest.chat_send_all("# Server: Player <" .. dname .. ">'s skill has been tested in a Survival Challenge and proved worthy.")
  493. minetest.chat_send_all("# Server: Player <" .. dname .. "> has earned " .. tokencount .. " " .. upperank .. " Skill Mark(s).")
  494. else
  495. minetest.chat_send_player(pname, "# Server: You have won the Survival Challenge!")
  496. end
  497. local inv = player:get_inventory()
  498. local leftover = inv:add_item("main", ItemStack("survivalist:" .. rank .. "_skill_token " .. tokencount))
  499. -- No room in inventory? Drop 'em.
  500. if leftover:get_count() > 0 then
  501. minetest.item_drop(leftover, player, pos)
  502. if not gdac.player_is_admin(pname) then
  503. minetest.chat_send_all("# Server: Player <" .. dname .. ">'s Skill Mark was dropped on the ground!")
  504. end
  505. end
  506. minetest.chat_send_player(pname, "# Server: You should have received a skill mark in your inventory. If your inventory was full, check near your feet!")
  507. -- Record that the challenge is over.
  508. survivalist.modstorage:set_string(pname .. ":mode", nil)
  509. survivalist.modstorage:set_string(pname .. ":pos", nil)
  510. survivalist.modstorage:set_string(pname .. ":home", nil)
  511. survivalist.modstorage:set_string(pname .. ":xp", nil)
  512. survivalist.players[pname].choice = nil
  513. -- Record total wins, win streaks, and win types.
  514. local ms = survivalist.modstorage
  515. local wins_total = pname .. ":wins_total"
  516. local wins_streak = pname .. ":wins_streak"
  517. local wins_bstreak = pname .. ":wins_bstreak"
  518. local wins_type = pname .. ":wins_" .. currentgame
  519. local wins_tokens = pname .. ":wins_tokens"
  520. ms:set_int(wins_total, ms:get_int(wins_total) + 1)
  521. ms:set_int(wins_streak, ms:get_int(wins_streak) + 1)
  522. ms:set_int(wins_type, ms:get_int(wins_type) + 1)
  523. ms:set_int(wins_tokens, ms:get_int(wins_tokens) + tokencount)
  524. -- If current streak is better than best streak, update best streak.
  525. -- Best streak is never erased.
  526. if ms:get_int(wins_streak) > ms:get_int(wins_bstreak) then
  527. ms:set_int(wins_bstreak, ms:get_int(wins_streak))
  528. end
  529. -- Grant player the big_hotbar priv.
  530. -- Rewarded by the 'surface' gamemode only.
  531. if currentgame == "surface" then
  532. if not minetest.check_player_privs(player, {big_hotbar=true}) then
  533. local privs = minetest.get_player_privs(pname)
  534. privs.big_hotbar = true
  535. minetest.set_player_privs(pname, privs)
  536. minetest.notify_authentication_modified(pname)
  537. player:hud_set_hotbar_image("gui_hotbar2.png")
  538. player:hud_set_hotbar_itemcount(16)
  539. end
  540. end
  541. -- Let everyone know the player's win stats.
  542. survivalist.shout_player_stats(pname)
  543. -- Add player's name to the rankings.
  544. survivalist.add_player_to_rankings(pname)
  545. end
  546. function survivalist.player_beat_cave_challenge(pname)
  547. local ms = survivalist.modstorage
  548. local va = ms:get_int(pname .. ":wins_cave") or 0
  549. if va > 0 then
  550. return true
  551. end
  552. return false
  553. end
  554. function survivalist.player_beat_nether_challenge(pname)
  555. local ms = survivalist.modstorage
  556. local va = ms:get_int(pname .. ":wins_nether") or 0
  557. if va > 0 then
  558. return true
  559. end
  560. return false
  561. end
  562. -- If a player joins the server and a Survivalist Challenge is running, inform them.
  563. function survivalist.on_joinplayer(player)
  564. local pname = player:get_player_name()
  565. survivalist.players[pname] = {}
  566. minetest.after(0.5, function()
  567. local player = minetest.get_player_by_name(pname)
  568. if not player then
  569. return
  570. end
  571. local gamemode = survivalist.modstorage:get_string(pname .. ":mode")
  572. if gamemode == "surface" or gamemode == "cave" or gamemode == "nether" then
  573. local dname = rename.gpn(pname)
  574. if not gdac.player_is_admin(pname) then
  575. minetest.chat_send_all("# Server: Player <" .. dname .. "> is engaged in a Survival Challenge.")
  576. end
  577. minetest.chat_send_player(pname, "# Server: You are in a Survival Challenge. Avoid death by sleeping!")
  578. minetest.chat_send_player(pname, "# Server: Find the Surface Colony or the Nether City and claim victory in the Central Square to beat the Challenge.")
  579. end
  580. end)
  581. end
  582. -- Dying cancels the challenge.
  583. function survivalist.on_dieplayer(player)
  584. local pname = player:get_player_name()
  585. -- If player has a bed respawn set, then they don't fail the challenge.
  586. if beds.has_respawn_bed(pname) then
  587. if survivalist.game_in_progress(pname) then
  588. minetest.after(1, function()
  589. minetest.chat_send_player(pname, "# Server: You should respawn in your bed. Warning: if you die without a bed, you will fail the Challenge.")
  590. end)
  591. end
  592. return
  593. end
  594. local gamemode = survivalist.modstorage:get_string(pname .. ":mode")
  595. if gamemode == "surface" or gamemode == "cave" or gamemode == "nether" then
  596. -- Delay the chat messages slightly.
  597. minetest.after(1, function()
  598. if not gdac.player_is_admin(pname) then
  599. local dname = rename.gpn(pname)
  600. minetest.chat_send_all("# Server: Player <" .. dname .. ">'s survival skill was tested and found wanting!")
  601. end
  602. survivalist.shout_player_stats(pname)
  603. minetest.chat_send_player(pname, "# Server: You failed the Survivalist Challenge. No goodies for you!")
  604. end)
  605. -- No game in progress. Win streak is reset to 0.
  606. survivalist.modstorage:set_string(pname .. ":mode", nil)
  607. survivalist.modstorage:set_string(pname .. ":pos", nil)
  608. survivalist.modstorage:set_string(pname .. ":home", nil)
  609. survivalist.modstorage:set_string(pname .. ":xp", nil)
  610. survivalist.modstorage:set_int(pname .. ":wins_streak", 0)
  611. -- Count loss.
  612. local ms = survivalist.modstorage
  613. local ff = pname .. ":wins_fail"
  614. ms:set_int(ff, ms:get_int(ff) + 1)
  615. -- Add player's name to the rankings (deaths count too).
  616. survivalist.add_player_to_rankings(pname)
  617. end
  618. end
  619. -- Abort a running game without loss to player score.
  620. function survivalist.abort_game(pname)
  621. local player = minetest.get_player_by_name(pname)
  622. if not player or not player:is_player() then
  623. return
  624. end
  625. local pmeta = player:get_meta()
  626. local gamemode = survivalist.modstorage:get_string(pname .. ":mode")
  627. if gamemode == "surface" or gamemode == "cave" or gamemode == "nether" then
  628. if not survivalist.check_inventories_empty(pname) then
  629. minetest.chat_send_player(pname, "# Server: All your inventories (including the Starfissure Box and armor) must be empty before you may cancel a challenge (the Proof of Citizenship does not count).")
  630. easyvend.sound_error(pname)
  631. return
  632. end
  633. minetest.chat_send_player(pname, "# Server: Challenge termination confirmed.")
  634. local target = rc.static_spawn("abyss")
  635. target = minetest.string_to_pos(survivalist.modstorage:get_string(pname .. ":home")) or target
  636. -- Pre-teleport callback function. We may need to abort to prevent cheating.
  637. local pcb = function()
  638. if not survivalist.check_inventories_empty(pname) then
  639. minetest.chat_send_player(pname, "# Server: All your inventories (including the Starfissure Box and armor) must be empty before you may cancel a challenge (the Proof of Citizenship does not count).")
  640. easyvend.sound_error(pname)
  641. -- Returning success means to abort the teleport.
  642. -- Only applicable to pre-teleport callbacks.
  643. return true
  644. end
  645. -- Abort if player is trying to cheat by sitting in a cart. >:)
  646. if default.player_attached[pname] then
  647. minetest.chat_send_player(pname, "# Server: Transport error. Player attached!")
  648. return true
  649. end
  650. end
  651. -- Don't clear the challenge gamestate unless teleport successful.
  652. local bcb = function()
  653. -- No game in progress.
  654. -- Do not touch scores.
  655. survivalist.modstorage:set_string(pname .. ":mode", nil)
  656. survivalist.modstorage:set_string(pname .. ":pos", nil)
  657. survivalist.modstorage:set_string(pname .. ":home", nil)
  658. survivalist.modstorage:set_string(pname .. ":xp", nil)
  659. -- UNCONDITIONALLY restore old bed position if available.
  660. --
  661. -- By overwriting whatever bed position the player had during the challenge,
  662. -- we avoid the problem were the player dies right after canceling, resulting
  663. -- in them being sent back to the challenge and being unable to get out again
  664. -- because the challenge is already canceled.
  665. --
  666. -- This does mean that if a player sets up a bed during a challenge and then
  667. -- cancels, they WILL NOT be able to return to their challenge location by dying,
  668. -- UNLESS they didn't have a bed when they BEGAN the challenge (because in that
  669. -- case, this code won't run).
  670. local bedpos = minetest.string_to_pos(pmeta:get_string("survivalist_old_bed_pos"))
  671. if bedpos then
  672. beds.set_player_spawn(pname, bedpos)
  673. end
  674. pmeta:set_string("survivalist_old_bed_pos", "")
  675. if not gdac.player_is_admin(pname) then
  676. local dname = rename.gpn(pname)
  677. minetest.chat_send_all("# Server: Player <" .. dname .. "> canceled a Survival Challenge and went home.")
  678. else
  679. minetest.chat_send_player(pname, "# Server: You canceled a Survival Challenge and went home.")
  680. end
  681. beds.report_respawn_status(pname)
  682. end
  683. -- Teleport is forced.
  684. preload_tp.execute({
  685. player_name = pname,
  686. target_position = target,
  687. emerge_radius = 32,
  688. pre_teleport_callback = pcb,
  689. post_teleport_callback = bcb,
  690. force_teleport = true,
  691. send_blocks = true,
  692. particle_effects = true,
  693. })
  694. else
  695. minetest.chat_send_player(pname, "# Server: You are not in a Survival Challenge; cannot abort.")
  696. easyvend.sound_error(pname)
  697. end
  698. end
  699. -- Compost the GUI formspec.
  700. function survivalist.compose_formspec(pname)
  701. local modestring = ""
  702. local inchallenge = false
  703. local gamemode = survivalist.modstorage:get_string(pname .. ":mode")
  704. if gamemode == "surface" then
  705. modestring = " (You are currently in an Iceworld Challenge)"
  706. inchallenge = true
  707. elseif gamemode == "cave" then
  708. modestring = " (You are currently in an Underearth Challenge)"
  709. inchallenge = true
  710. elseif gamemode == "nether" then
  711. modestring = " (You are currently in a Netherealm Challenge)"
  712. inchallenge = true
  713. end
  714. local type_surface = "false"
  715. local type_cave = "false"
  716. local type_nether = "false"
  717. -- Choose which checkbox will be shown as selected.
  718. if survivalist.players[pname] and survivalist.players[pname].choice then
  719. local choice = survivalist.players[pname].choice
  720. if choice == "surface" then
  721. type_surface = "true"
  722. elseif choice == "cave" then
  723. type_cave = "true"
  724. elseif choice == "nether" then
  725. type_nether = "true"
  726. end
  727. end
  728. local formspec = ""
  729. formspec = formspec .. "size[8,7]" ..
  730. default.gui_bg ..
  731. default.gui_bg_img ..
  732. default.gui_slots ..
  733. "label[0,0;Survivalist Challenge" .. modestring .. "]" ..
  734. "textarea[0.3,0.5;8,4;rules;;" .. minetest.formspec_escape(survivalist.gamerules) .. "]"
  735. -- Show challenge choices only if no challenge is in progress.
  736. if not inchallenge and survivalist.have_xp(pname, "minimum") then
  737. formspec = formspec .. "label[0,4;Choose Your Challenge!]"
  738. if survivalist.have_xp(pname, "surface") then
  739. formspec = formspec .. "checkbox[0,4.3;type_surface;Surface Survival (Copper Mark);" .. type_surface .. "]"
  740. end
  741. if survivalist.have_xp(pname, "cave") then
  742. formspec = formspec .. "checkbox[0,4.8;type_cave;Cave Survival (Silver Mark);" .. type_cave .. "]"
  743. end
  744. if survivalist.have_xp(pname, "nether") then
  745. formspec = formspec .. "checkbox[0,5.3;type_nether;Nether Survival (Gold Mark);" .. type_nether .. "]"
  746. end
  747. end
  748. -- Choose between start/abort buttons.
  749. if inchallenge then
  750. formspec = formspec .. "button[0,6.2;2,1;abort;Go Home]"
  751. else
  752. if survivalist.have_xp(pname, "minimum") then
  753. formspec = formspec .. "button[0,6.2;3,1;start;Begin Challenge]"
  754. end
  755. end
  756. -- Show `Claim Victory` only if a challenge is in progress.
  757. if inchallenge then
  758. formspec = formspec .. "button[2,6.2;2,1;victory;Claim Victory]"
  759. end
  760. formspec = formspec .. "button[4,6.2;2,1;show_rankings;Rankings]" ..
  761. "button[6,6.2;2,1;close;Close]"
  762. return formspec
  763. end
  764. function survivalist.compose_rankings_formspec(pname)
  765. local players = survivalist.get_ranking_entries()
  766. local data = {}
  767. local ms = survivalist.modstorage
  768. for k, v in pairs(players) do
  769. data[#data+1] = {
  770. name = k,
  771. wins = ms:get_int(k .. ":wins_total"),
  772. deaths = ms:get_int(k .. ":wins_fail"),
  773. streak = ms:get_int(k .. ":wins_streak"),
  774. bstreak = ms:get_int(k .. ":wins_bstreak"),
  775. surface = ms:get_int(k .. ":wins_surface"),
  776. cave = ms:get_int(k .. ":wins_cave"),
  777. nether = ms:get_int(k .. ":wins_nether"),
  778. tokens = ms:get_int(k .. ":wins_tokens"),
  779. }
  780. end
  781. local form = ""
  782. form = form .. "size[13,7]" ..
  783. default.gui_bg ..
  784. default.gui_bg_img ..
  785. default.gui_slots ..
  786. "label[0,0;Survival Challenge Rankings]" ..
  787. "button[11,6.2;2,1;close_rankings;Close]"
  788. form = form .. "tablecolumns[color;text;text;text;text;text;text;text;text;text;text;text;text;text;text;text;text;text]"
  789. form = form .. "table[0,0.5;12.8,5;rankings_table;"
  790. form = form .. "#00ffff,Username,|,Victories,|,Deaths,|,C-Streak,|,B-Streak,|,Iceworld,|,Underearth,|,Netherealm,|,Marks,"
  791. for k, v in ipairs(data) do
  792. form = form .. ",<" .. rename.gpn(v.name) .. ">,|," .. v.wins .. ",|," .. v.deaths .. ",|," ..
  793. v.streak .. ",|," .. v.bstreak .. ",|," .. v.surface .. ",|," .. v.cave ..
  794. ",|," .. v.nether .. ",|," .. v.tokens .. ","
  795. end
  796. form = string.gsub(form, ",$", "")
  797. form = form .. ";1]"
  798. return form
  799. end
  800. function survivalist.show_rankings_formspec(pname)
  801. local formspec = survivalist.compose_rankings_formspec(pname)
  802. minetest.show_formspec(pname, "survivalist:rankings", formspec)
  803. end
  804. -- API function (called from passport mod, for instance).
  805. function survivalist.show_formspec(pname)
  806. local formspec = survivalist.compose_formspec(pname)
  807. minetest.show_formspec(pname, "survivalist:survivalist", formspec)
  808. end
  809. -- GUI form input handler.
  810. function survivalist.on_receive_fields(player, formname, fields)
  811. local pname = player:get_player_name()
  812. if formname ~= "survivalist:survivalist" and formname ~= "survivalist:rankings" then
  813. return
  814. end
  815. if fields.quit then
  816. return true
  817. end
  818. if fields.show_rankings then
  819. survivalist.show_rankings_formspec(pname)
  820. return true
  821. end
  822. if fields.rankings_table then
  823. survivalist.show_rankings_formspec(pname)
  824. return true
  825. end
  826. if fields.close_rankings or formname == "survivalist:rankings" then
  827. survivalist.show_formspec(pname)
  828. return true
  829. end
  830. -- Start game.
  831. if fields.start and survivalist.have_xp(pname, "minimum") then
  832. minetest.close_formspec(pname, "survivalist:survivalist")
  833. survivalist.start_game(pname, survivalist.players[pname].choice)
  834. return true
  835. end
  836. -- Claim victory.
  837. if fields.victory then
  838. minetest.close_formspec(pname, "survivalist:survivalist")
  839. survivalist.attempt_claim(pname)
  840. return true
  841. end
  842. -- Abort challenge.
  843. if fields.abort then
  844. minetest.close_formspec(pname, "survivalist:survivalist")
  845. survivalist.abort_game(pname)
  846. return true
  847. end
  848. -- Handle gamemode checkboxes.
  849. for j, k in ipairs({"surface", "cave", "nether"}) do
  850. if fields["type_" .. k] then
  851. if fields["type_" .. k] == "true" then
  852. survivalist.players[pname].choice = k
  853. else
  854. survivalist.players[pname].choice = nil
  855. end
  856. survivalist.show_formspec(pname)
  857. return true
  858. end
  859. end
  860. if fields.close then
  861. --minetest.close_formspec(pname, "survivalist:survivalist")
  862. passport.show_formspec(pname)
  863. return true
  864. end
  865. survivalist.show_formspec(pname)
  866. return true
  867. end
  868. -- Here belongs code which must run only once.
  869. if not survivalist.run_once then
  870. -- Obtain modstorage.
  871. survivalist.modstorage = minetest.get_mod_storage()
  872. minetest.register_privilege("big_hotbar", {
  873. description = "Player has double-wide hotbar for item wielding.",
  874. give_to_singleplayer = false,
  875. })
  876. -- Define the victory tokens.
  877. local nodebox = {
  878. type = "fixed",
  879. fixed = {
  880. {-0.5, -0.5, -0.5, 0.5, -15/32, 0.5},
  881. },
  882. }
  883. for j, k in ipairs({
  884. {name="copper", upper="Copper"},
  885. {name="silver", upper="Silver"},
  886. {name="gold", upper="Gold"},
  887. }) do
  888. minetest.register_node("survivalist:" .. k.name .. "_skill_token", {
  889. description = k.upper .. " Skill Mark",
  890. tiles = {"survivalist_" .. k.name .. "_token.png"},
  891. inventory_image = "survivalist_" .. k.name .. "_token.png",
  892. wield_image = "survivalist_" .. k.name .. "_token.png",
  893. -- If stack_max is clamped, then players can lose marks they win due to
  894. -- stack clamping, when marks are placed in inventory or dropped on the ground.
  895. --stack_max = 1,
  896. paramtype = "light",
  897. paramtype2 = "facedir",
  898. sunlight_propagates = true,
  899. walkable = false,
  900. groups = utility.dig_groups("item"),
  901. sounds = default.node_sound_metal_defaults(),
  902. drawtype = "nodebox",
  903. node_box = nodebox,
  904. selection_box = nodebox,
  905. on_place = minetest.rotate_node,
  906. })
  907. end
  908. -- Compatibility alias.
  909. minetest.register_alias("survivalist:skill_token", "survivalist:silver_skill_token")
  910. -- GUI input handler.
  911. minetest.register_on_player_receive_fields(function(...)
  912. return survivalist.on_receive_fields(...)
  913. end)
  914. -- Update state for players that join.
  915. minetest.register_on_joinplayer(function(...)
  916. return survivalist.on_joinplayer(...)
  917. end)
  918. -- Dead players can't win anything.
  919. minetest.register_on_dieplayer(function(...)
  920. return survivalist.on_dieplayer(...)
  921. end)
  922. -- File is reloadable.
  923. local c = "survivalist:core"
  924. local f = survivalist.modpath .. "/init.lua"
  925. reload.register_file(c, f, false)
  926. -- Done.
  927. survivalist.run_once = true
  928. end