FILES ----- [NEW] debug.macros dungeon_worlds.config instance_worlds.config player.config terrestrial_worlds.config behaviors\npc\accuse.behavior behaviors\npc\attackthief.behavior [NEW] dungeons\microdungeons\bounty\fuel_depot.dungeon [NEW] dungeons\microdungeons\bounty\fuel_depot.json [NEW] dungeons\microdungeons\bounty\fuel_depot_1.json [NEW] dungeons\microdungeons\bounty\fuel_depot_2.json [NEW] dungeons\microdungeons\bounty\fuel_depot_3.json [NEW] dungeons\microdungeons\bounty\rescue_1.json dungeons\microdungeons\bounty\safehouse.dungeon [NEW] dungeons\microdungeons\bounty\safehouse_20.json dungeons\microdungeons\bounty\safehouse_template.json [NEW] dungeons\microdungeons\bounty\wavedefense_1.json [NEW] dungeons\other\cyberspace\cyberdungeon.dungeon [NEW] dungeons\other\cyberspace\cyberdungeon_middle_1.json [NEW] dungeons\other\cyberspace\cyberdungeon_middle_2.json [NEW] dungeons\other\cyberspace\cyberdungeon_start_1.json [NEW] dungeons\other\cyberspace\cyberdungeon_start_2.json [NEW] dungeons\other\cyberspace\cyberdungeon_start_3.json [NEW] dungeons\other\cyberspace\start_cyberspace.dungeon [NEW] dungeons\other\cyberspace\start_cyberspace.json interface\cockpit\cockpitutil.lua interface\optionsmenu\optionsmenu.config interface\scripted\bountyboard\bountyboardgui.config interface\scripted\bountyboard\bountyboardgui.lua [NEW] interface\statuses\glue.png [NEW] interface\windowconfig\joinrequest.config [NEW] items\active\weapons\ranged\unrand\gluesprayer\gluesprayer.activeitem [NEW] items\active\weapons\ranged\unrand\gluesprayer\gluesprayer.animation [NEW] items\active\weapons\ranged\unrand\gluesprayer\muzzleflash.frames [NEW] items\active\weapons\ranged\unrand\gluesprayer\muzzleflash.png [NEW] items\materials\polygonplatform.matitem [NEW] items\materials\polygonplatformicon.png [NEW] items\throwables\stungrenade.png [NEW] items\throwables\stungrenade.thrownitem [NEW] npcs\furniture\egyptian.npctype [NEW] objects\bounty\scanclue2\scanclue2.frames [NEW] objects\bounty\scanclue2\scanclue2.object [NEW] objects\bounty\scanclue2\scanclue2.png [NEW] objects\bounty\scanclue2\scanclue2icon.png objects\outpost\bountyboard\bountyboard.lua objects\outpost\bountyboard\bountyboard.object objects\themed\egyptian\egyptianbed\egyptianbed.object objects\themed\egyptian\egyptianbed\egyptianbedcover.png [NEW] objects\themed\egyptian\egyptianlamp\egyptianlamp.animation [NEW] objects\themed\egyptian\egyptianlamp\egyptianlamp.frames [NEW] objects\themed\egyptian\egyptianlamp\egyptianlamp.object [NEW] objects\themed\egyptian\egyptianlamp\egyptianlampicon.png [NEW] objects\themed\egyptian\egyptianlamp\egyptianlamplit.frames [NEW] objects\themed\egyptian\egyptianlamp\egyptianlamplit.png objects\themed\egyptian\egyptianpillar\egyptianpillar.object objects\themed\egyptian\egyptianstatuette\egyptianstatuette.object parallax\images\cyberspace2\base\1.png [NEW] parallax\images\cyberspace2\base\2.png [NEW] parallax\images\cyberspace2\base\3.png [NEW] parallax\images\cyberspace3\base\1.png [NEW] parallax\images\cyberspace3\base\2.png [NEW] parallax\images\cyberspace3\base\3.png [NEW] parallax\images\cyberspace3\base\4.png [NEW] parallax\images\cyberspace3\base\5.png [NEW] parallax\images\cyberspace3\base\6.png [NEW] parallax\images\cyberspace4\base\1.png [NEW] parallax\images\cyberspace4\base\2.png [NEW] parallax\images\cyberspace5\base\1.png [NEW] parallax\images\cyberspace5\base\2.png [NEW] parallax\images\cyberspace5\base\3.png parallax\surface\cyberspace.parallax [NEW] projectiles\activeitems\gluespray\gluespray.frames [NEW] projectiles\activeitems\gluespray\gluespray.png [NEW] projectiles\activeitems\gluespray\gluespray.projectile [NEW] projectiles\activeitems\gluespray\icon.png [NEW] projectiles\throwable\stungrenade\stungrenade.frames [NEW] projectiles\throwable\stungrenade\stungrenade.png [NEW] projectiles\throwable\stungrenade\stungrenade.projectile quests\bounty\bounty.lua quests\bounty\bountyassignment.lua quests\bounty\bounty_gen.lua quests\bounty\bounty_mission.questtemplate quests\bounty\capture_bounty.questtemplate quests\bounty\capture_ship_bounty.questtemplate quests\bounty\capture_space_bounty.questtemplate quests\bounty\clue_items.config quests\bounty\clue_scans.config quests\bounty\find_clue_item.questtemplate quests\bounty\generator.config quests\bounty\kill_bounty_monster.questtemplate [NEW] quests\bounty\pre_bounty.questtemplate quests\bounty\pre_bounty_capstone.questtemplate quests\bounty\pre_bounty_minor_monster.questtemplate quests\bounty\pre_bounty_minor_npc.questtemplate quests\bounty\scan_planets.questtemplate quests\bounty\stages.lua [NEW] recipes\peacekeeperstore\gluesprayer.recipe recipes\peacekeeperstore\mechbodypeacekeeper.recipe [NEW] recipes\peacekeeperstore\neolaserlauncher.recipe recipes\peacekeeperstore\peacekeeperchest.recipe recipes\peacekeeperstore\peacekeeperhead.recipe recipes\peacekeeperstore\peacekeeperpants.recipe scripts\bountygeneration.lua [NEW] scripts\player\playerbounty.lua [NEW] scripts\player\stealing.lua stagehands\bountymanager.lua stats\effects\capturebeamout\capturebeamout.png [NEW] stats\effects\glueslow\glueslow.animation [NEW] stats\effects\glueslow\glueslow.lua [NEW] stats\effects\glueslow\glueslow.statuseffect [NEW] tenants\furniture\egyptian.tenant tenants\other\peacekeepertenant.tenant tiles\materials\slopedpolygon.material tiles\materials\slopedpolygon.png [NEW] tiles\platforms\polygonplatform.material [NEW] tiles\platforms\polygonplatform.png tilesets\packed\materials.json tilesets\packed\supports.json tilesets\packed\objects-by-category\decorative.json tilesets\packed\objects-by-category\furniture.json tilesets\packed\objects-by-category\light.json tilesets\packed\objects-by-category\storage.json tilesets\packed\objects-by-category\wire.json tilesets\packed\objects-by-colonytag\commerce.json tilesets\packed\objects-by-colonytag\egyptian.json tilesets\packed\objects-by-colonytag\electronic.json tilesets\packed\objects-by-colonytag\light.json tilesets\packed\objects-by-colonytag\misc.json tilesets\packed\objects-by-colonytag\office.json tilesets\packed\objects-by-colonytag\peacekeeper.json tilesets\packed\objects-by-colonytag\pretty.json tilesets\packed\objects-by-colonytag\storage.json tilesets\packed\objects-by-colonytag\wired.json tilesets\packed\objects-by-race\generic.json tilesets\packed\objects-by-type\container.json tilesets\packed\objects-by-type\loungeable.json treasure\space.treasurepools [NEW] versioning\PlayerEntity_30_31.lua DIFFS ----- dungeon_worlds.config 209,210c209 < "ambientNoises" : "/sfx/environmental/space_loop4.ogg", < "musicTrack" : "/music/arctic-battle2-loop.ogg", --- > "musicTrack" : "/music/housecleaning.ogg", 678c677 < "musicTrack" : "/music/i-was-the-sun.ogg", --- > "musicTrack" : "/music/housecleaning.ogg", 691c690 < "musicTrack" : "/music/i-was-the-sun.ogg", --- > "musicTrack" : "/music/housecleaning.ogg", 704c703 < "musicTrack" : "/music/i-was-the-sun.ogg", --- > "musicTrack" : "/music/housecleaning.ogg", 720c719 < "musicTrack" : "/music/i-was-the-sun.ogg", --- > "musicTrack" : "/music/housecleaning.ogg", instance_worlds.config 1338c1338 < "persistent" : false, --- > "persistent" : true, 1370c1370 < "persistent" : false, --- > "persistent" : true, 1402c1402 < "persistent" : false, --- > "persistent" : true, 1466c1466 < "persistent" : false, --- > "persistent" : true, player.config 782,783c782,783 < //"events" : "/events/events.lua", < "bounty" : "/scripts/bounty/playerbounty.lua" --- > "bounty" : "/scripts/player/playerbounty.lua", > "stealing" : "/scripts/player/stealing.lua" terrestrial_worlds.config 659c659,660 < "dungeons" : [] --- > "dungeonCountRange" : [2, 2], > "dungeons" : [[1.0, "start_cyberspace"], [1.0, "cyberdungeon"]] 662c663 < "primaryRegion" : ["tentacleundergroundtentacles"] --- > "primaryRegion" : ["void"] 665c666 < "primaryRegion" : ["tentacleundergroundtentacles"], --- > "primaryRegion" : ["void"], 669c670 < "primaryRegion" : ["tentacleundergroundceiling"], --- > "primaryRegion" : ["void"], 673,676c674,675 < "primaryRegion" : ["tentacleundergroundfloor"], < "secondaryRegions" : [], < "dungeons" : [[1.0, "tentacleboss"]], < "dungeonCountRange" : [1, 1] --- > "primaryRegion" : ["void"], > "secondaryRegions" : [] 679c678 < "primaryRegion" : ["tentacleundergroundcore"], --- > "primaryRegion" : ["void"], behaviors\npc\accuse.behavior 5,8d4 < "/scripts/actions/status.lua", < "/scripts/actions/math.lua", < "/scripts/actions/world.lua", < "/scripts/actions/entity.lua", 9a6 > "/scripts/actions/entity.lua", 11a9 > "/scripts/actions/world.lua", 20c18 < "title": "dynamic", --- > "title": "sequence", 22c20 < "name": "dynamic", --- > "name": "sequence", 26c24 < "title": "sequence", --- > "title": "selector", 28c26 < "name": "sequence", --- > "name": "selector", 32,43c30,38 < "title": "inverter", < "type": "decorator", < "name": "inverter", < "parameters": {}, < "child": { < "title": "resourcePercentage", < "type": "action", < "name": "resourcePercentage", < "parameters": { < "percentage": {"value": 0}, < "resource": {"value": "health"} < } --- > "title": "receivedNotification", > "type": "action", > "name": "receivedNotification", > "parameters": { > "type": {"value": "objectBroken"} > }, > "output": { > "target": "thiefTarget", > "targetPosition": "objectPosition" 47,75c42,50 < "title": "succeeder", < "type": "decorator", < "name": "succeeder", < "parameters": {}, < "child": { < "title": "sequence", < "type": "composite", < "name": "sequence", < "parameters": {}, < "children": [ < { < "title": "chance", < "type": "action", < "name": "chance", < "parameters": { < "chance": {"value": 0.25} < } < }, < { < "title": "sendEntityMessage", < "type": "action", < "name": "sendEntityMessage", < "parameters": { < "arguments": {"value": [3]}, < "entity": {"key": "thiefTarget"}, < "message": {"value": "raiseWantedLevel"} < } < } < ] --- > "title": "receivedNotification", > "type": "action", > "name": "receivedNotification", > "parameters": { > "type": {"value": "tileBroken"} > }, > "output": { > "target": "thiefTarget", > "targetPosition": "objectPosition" 79c54 < "title": "setDying", --- > "title": "receivedNotification", 81c56 < "name": "setDying", --- > "name": "receivedNotification", 83c58,62 < "shouldDie": {"value": true} --- > "type": {"value": "itemStolen"} > }, > "output": { > "target": "thiefTarget", > "targetPosition": "objectPosition" 89c68,76 < "title": "sequence", --- > "title": "setDying", > "type": "action", > "name": "setDying", > "parameters": { > "shouldDie": {"value": false} > } > }, > { > "title": "selector", 91c78 < "name": "sequence", --- > "name": "selector", 95c82 < "title": "selector", --- > "title": "sequence", 97c84 < "name": "selector", --- > "name": "sequence", 101c88 < "title": "receivedNotification", --- > "title": "distance", 103c90 < "name": "receivedNotification", --- > "name": "distance", 105c92,93 < "type": {"value": "objectBroken"} --- > "from": {"key": "self"}, > "to": {"key": "objectPosition"} 108,109c96 < "target": "thiefTarget", < "targetPosition": "objectPosition" --- > "vector": "toObject" 113c100,124 < "title": "receivedNotification", --- > "title": "inverter", > "type": "decorator", > "name": "inverter", > "parameters": {}, > "child": { > "title": "lineTileCollision", > "type": "action", > "name": "lineTileCollision", > "parameters": { > "collisionType": {"value": ["Null", "Block", "Dynamic"]}, > "offset": {"key": "toObject"}, > "position": {"key": "self"} > } > } > } > ] > }, > { > "title": "sequence", > "type": "composite", > "name": "sequence", > "parameters": {}, > "children": [ > { > "title": "entityPosition", 115c126 < "name": "receivedNotification", --- > "name": "entityPosition", 117c128 < "type": {"value": "tileBroken"} --- > "entity": {"key": "thiefTarget"} 120,121c131 < "target": "thiefTarget", < "targetPosition": "objectPosition" --- > "position": "thiefPosition" 125c135 < "title": "receivedNotification", --- > "title": "distance", 127c137 < "name": "receivedNotification", --- > "name": "distance", 129c139,140 < "type": {"value": "itemStolen"} --- > "from": {"key": "self"}, > "to": {"key": "thiefPosition"} 132,133c143 < "target": "thiefTarget", < "targetPosition": "objectPosition" --- > "vector": "toThief" 135,196d144 < } < ] < }, < { < "title": "setDying", < "type": "action", < "name": "setDying", < "parameters": { < "shouldDie": {"value": false} < } < }, < { < "title": "sendEntityMessage", < "type": "action", < "name": "sendEntityMessage", < "parameters": { < "arguments": {"value": [1]}, < "entity": {"key": "thiefTarget"}, < "message": {"value": "raiseWantedLevel"} < } < }, < { < "title": "selector", < "type": "composite", < "name": "selector", < "parameters": {}, < "children": [ < { < "title": "sequence", < "type": "composite", < "name": "sequence", < "parameters": {}, < "children": [ < { < "title": "distance", < "type": "action", < "name": "distance", < "parameters": { < "from": {"key": "self"}, < "to": {"key": "objectPosition"} < }, < "output": { < "vector": "toObject" < } < }, < { < "title": "inverter", < "type": "decorator", < "name": "inverter", < "parameters": {}, < "child": { < "title": "lineTileCollision", < "type": "action", < "name": "lineTileCollision", < "parameters": { < "collisionType": {"value": ["Null", "Block", "Dynamic"]}, < "offset": {"key": "toObject"}, < "position": {"key": "self"} < } < } < } < ] 199,201c147,149 < "title": "sequence", < "type": "composite", < "name": "sequence", --- > "title": "inverter", > "type": "decorator", > "name": "inverter", 203,241c151,158 < "children": [ < { < "title": "entityPosition", < "type": "action", < "name": "entityPosition", < "parameters": { < "entity": {"key": "thiefTarget"} < }, < "output": { < "position": "thiefPosition" < } < }, < { < "title": "distance", < "type": "action", < "name": "distance", < "parameters": { < "from": {"key": "self"}, < "to": {"key": "thiefPosition"} < }, < "output": { < "vector": "toThief" < } < }, < { < "title": "inverter", < "type": "decorator", < "name": "inverter", < "parameters": {}, < "child": { < "title": "lineTileCollision", < "type": "action", < "name": "lineTileCollision", < "parameters": { < "collisionType": {"value": ["Null", "Block", "Dynamic"]}, < "offset": {"key": "toThief"}, < "position": {"key": "self"} < } < } --- > "child": { > "title": "lineTileCollision", > "type": "action", > "name": "lineTileCollision", > "parameters": { > "collisionType": {"value": ["Null", "Block", "Dynamic"]}, > "offset": {"key": "toThief"}, > "position": {"key": "self"} 243c160 < ] --- > } 245a163,188 > } > ] > }, > { > "title": "selector", > "type": "composite", > "name": "selector", > "parameters": {}, > "children": [ > { > "title": "limiter", > "type": "decorator", > "name": "limiter", > "parameters": { > "limit": {"value": 2} > }, > "child": { > "title": "sayToEntity", > "type": "action", > "name": "sayToEntity", > "parameters": { > "dialogType": {"value": "dialog.accuse"}, > "entity": {"key": "thiefTarget"}, > "tags": {"value": {}} > } > } 248c191 < "title": "selector", --- > "title": "sequence", 250c193 < "name": "selector", --- > "name": "sequence", 254,256c197,199 < "title": "limiter", < "type": "decorator", < "name": "limiter", --- > "title": "setEntity", > "type": "action", > "name": "setEntity", 258c201 < "limit": {"value": 2} --- > "entity": {"key": "thiefTarget"} 260,268c203,212 < "child": { < "title": "sayToEntity", < "type": "action", < "name": "sayToEntity", < "parameters": { < "dialogType": {"value": "dialog.accuse"}, < "entity": {"key": "thiefTarget"}, < "tags": {"value": {}} < } --- > "output": { > "entity": "reactTarget" > } > }, > { > "title": "setDamageTeam", > "type": "action", > "name": "setDamageTeam", > "parameters": { > "damageTeam": {"key": ""} 272c216 < "title": "sequence", --- > "title": "parallel", 274,275c218,222 < "name": "sequence", < "parameters": {}, --- > "name": "parallel", > "parameters": { > "fail": {"value": -1}, > "success": {"value": -1} > }, 278,280c225,227 < "title": "setEntity", < "type": "action", < "name": "setEntity", --- > "title": "cooldown", > "type": "decorator", > "name": "cooldown", 282c229,231 < "entity": {"key": "thiefTarget"} --- > "cooldown": {"value": 1}, > "onFail": {"value": false}, > "onSuccess": {"value": true} 284,303c233,243 < "output": { < "entity": "reactTarget" < } < }, < { < "title": "sendEntityMessage", < "type": "action", < "name": "sendEntityMessage", < "parameters": { < "arguments": {"value": [2]}, < "entity": {"key": "thiefTarget"}, < "message": {"value": "raiseWantedLevel"} < } < }, < { < "title": "setDamageTeam", < "type": "action", < "name": "setDamageTeam", < "parameters": { < "damageTeam": {"key": ""} --- > "child": { > "title": "broadcastNotification", > "type": "action", > "name": "broadcastNotification", > "parameters": { > "entityTypes": {"value": ["npc"]}, > "position": {"key": "self"}, > "range": {"value": 40}, > "target": {"key": "reactTarget"}, > "type": {"value": "attackThief"} > } 307,309c247,249 < "title": "parallel", < "type": "composite", < "name": "parallel", --- > "title": "cooldown", > "type": "decorator", > "name": "cooldown", 311,312c251,253 < "fail": {"value": -1}, < "success": {"value": -1} --- > "cooldown": {"value": 6}, > "onFail": {"value": false}, > "onSuccess": {"value": true} 314,325c255,262 < "children": [ < { < "title": "cooldown", < "type": "decorator", < "name": "cooldown", < "parameters": { < "cooldown": {"value": 1}, < "onFail": {"value": false}, < "onSuccess": {"value": true} < }, < "child": { < "title": "broadcastNotification", --- > "child": { > "title": "sequence", > "type": "composite", > "name": "sequence", > "parameters": {}, > "children": [ > { > "title": "timer", 327c264 < "name": "broadcastNotification", --- > "name": "timer", 329,333c266 < "entityTypes": {"value": ["npc"]}, < "position": {"key": "self"}, < "range": {"value": 40}, < "target": {"key": "reactTarget"}, < "type": {"value": "attackThief"} --- > "time": {"value": 2} 335,344d267 < } < }, < { < "title": "cooldown", < "type": "decorator", < "name": "cooldown", < "parameters": { < "cooldown": {"value": 6}, < "onFail": {"value": false}, < "onSuccess": {"value": true} 346,379c269,277 < "child": { < "title": "sequence", < "type": "composite", < "name": "sequence", < "parameters": {}, < "children": [ < { < "title": "timer", < "type": "action", < "name": "timer", < "parameters": { < "time": {"value": 2} < } < }, < { < "title": "sayToEntity", < "type": "action", < "name": "sayToEntity", < "parameters": { < "dialogType": {"value": "dialog.alert"}, < "entity": {"key": "reactTarget"}, < "tags": {"value": {}} < } < } < ] < } < }, < { < "title": "reaction-follow", < "type": "module", < "name": "reaction-follow", < "parameters": { < "creepy": {"value": false}, < "duration": {"value": 10} --- > { > "title": "sayToEntity", > "type": "action", > "name": "sayToEntity", > "parameters": { > "dialogType": {"value": "dialog.alert"}, > "entity": {"key": "reactTarget"}, > "tags": {"value": {}} > } 381,382c279,289 < } < ] --- > ] > } > }, > { > "title": "reaction-follow", > "type": "module", > "name": "reaction-follow", > "parameters": { > "creepy": {"value": false}, > "duration": {"value": 10} > } behaviors\npc\attackthief.behavior 5,8d4 < "/scripts/actions/status.lua", < "/scripts/actions/math.lua", < "/scripts/actions/world.lua", < "/scripts/actions/entity.lua", 11a8 > "/scripts/actions/world.lua", 17,19c14,16 < "title": "dynamic", < "type": "composite", < "name": "dynamic", --- > "title": "failer", > "type": "decorator", > "name": "failer", 21,34c18,31 < "children": [ < { < "title": "sequence", < "type": "composite", < "name": "sequence", < "parameters": {}, < "children": [ < { < "title": "inverter", < "type": "decorator", < "name": "inverter", < "parameters": {}, < "child": { < "title": "resourcePercentage", --- > "child": { > "title": "sequence", > "type": "composite", > "name": "sequence", > "parameters": {}, > "children": [ > { > "title": "selector", > "type": "composite", > "name": "selector", > "parameters": {}, > "children": [ > { > "title": "receivedNotification", 36c33 < "name": "resourcePercentage", --- > "name": "receivedNotification", 38,39c35,63 < "percentage": {"value": 0}, < "resource": {"value": "health"} --- > "type": {"value": "objectBroken"} > }, > "output": { > "target": "thiefTarget", > "targetPosition": "objectPosition" > } > }, > { > "title": "receivedNotification", > "type": "action", > "name": "receivedNotification", > "parameters": { > "type": {"value": "tileBroken"} > }, > "output": { > "target": "thiefTarget", > "targetPosition": "objectPosition" > } > }, > { > "title": "receivedNotification", > "type": "action", > "name": "receivedNotification", > "parameters": { > "type": {"value": "itemStolen"} > }, > "output": { > "target": "thiefTarget", > "targetPosition": "objectPosition" 42,93c66,71 < }, < { < "title": "succeeder", < "type": "decorator", < "name": "succeeder", < "parameters": {}, < "child": { < "title": "sequence", < "type": "composite", < "name": "sequence", < "parameters": {}, < "children": [ < { < "title": "chance", < "type": "action", < "name": "chance", < "parameters": { < "chance": {"value": 0.5} < } < }, < { < "title": "sendEntityMessage", < "type": "action", < "name": "sendEntityMessage", < "parameters": { < "arguments": {"value": [3]}, < "entity": {"key": "thiefTarget"}, < "message": {"value": "raiseWantedLevel"} < } < } < ] < } < }, < { < "title": "setDying", < "type": "action", < "name": "setDying", < "parameters": { < "shouldDie": {"value": true} < } < } < ] < }, < { < "title": "failer", < "type": "decorator", < "name": "failer", < "parameters": {}, < "child": { < "title": "sequence", < "type": "composite", < "name": "sequence", --- > ] > }, > { > "title": "inverter", > "type": "decorator", > "name": "inverter", 95,107c73,95 < "children": [ < { < "title": "selector", < "type": "composite", < "name": "selector", < "parameters": {}, < "children": [ < { < "title": "receivedNotification", < "type": "action", < "name": "receivedNotification", < "parameters": { < "type": {"value": "objectBroken"} --- > "child": { > "title": "selector", > "type": "composite", > "name": "selector", > "parameters": {}, > "children": [ > { > "title": "sequence", > "type": "composite", > "name": "sequence", > "parameters": {}, > "children": [ > { > "title": "distance", > "type": "action", > "name": "distance", > "parameters": { > "from": {"key": "self"}, > "to": {"key": "objectPosition"} > }, > "output": { > "vector": "toObject" > } 109,111c97,105 < "output": { < "target": "thiefTarget", < "targetPosition": "objectPosition" --- > { > "title": "lineTileCollision", > "type": "action", > "name": "lineTileCollision", > "parameters": { > "collisionType": {"value": ["Null", "Block", "Dynamic"]}, > "offset": {"key": "toObject"}, > "position": {"key": "self"} > } 113,119c107,124 < }, < { < "title": "receivedNotification", < "type": "action", < "name": "receivedNotification", < "parameters": { < "type": {"value": "tileBroken"} --- > ] > }, > { > "title": "sequence", > "type": "composite", > "name": "sequence", > "parameters": {}, > "children": [ > { > "title": "entityPosition", > "type": "action", > "name": "entityPosition", > "parameters": { > "entity": {"key": "thiefTarget"} > }, > "output": { > "position": "thiefPosition" > } 121,131c126,136 < "output": { < "target": "thiefTarget", < "targetPosition": "objectPosition" < } < }, < { < "title": "receivedNotification", < "type": "action", < "name": "receivedNotification", < "parameters": { < "type": {"value": "itemStolen"} --- > { > "title": "distance", > "type": "action", > "name": "distance", > "parameters": { > "from": {"key": "self"}, > "to": {"key": "thiefPosition"} > }, > "output": { > "vector": "toThief" > } 133,135c138,146 < "output": { < "target": "thiefTarget", < "targetPosition": "objectPosition" --- > { > "title": "lineTileCollision", > "type": "action", > "name": "lineTileCollision", > "parameters": { > "collisionType": {"value": ["Null", "Block", "Dynamic"]}, > "offset": {"key": "toThief"}, > "position": {"key": "self"} > } 137,145c148 < } < ] < }, < { < "title": "setDying", < "type": "action", < "name": "setDying", < "parameters": { < "shouldDie": {"value": false} --- > ] 147c150,158 < }, --- > ] > } > }, > { > "title": "selector", > "type": "composite", > "name": "selector", > "parameters": {}, > "children": [ 149c160 < "title": "inverter", --- > "title": "limiter", 151,152c162,165 < "name": "inverter", < "parameters": {}, --- > "name": "limiter", > "parameters": { > "limit": {"value": 2} > }, 154c167 < "title": "selector", --- > "title": "sequence", 156c169 < "name": "selector", --- > "name": "sequence", 160,187c173,180 < "title": "sequence", < "type": "composite", < "name": "sequence", < "parameters": {}, < "children": [ < { < "title": "distance", < "type": "action", < "name": "distance", < "parameters": { < "from": {"key": "self"}, < "to": {"key": "objectPosition"} < }, < "output": { < "vector": "toObject" < } < }, < { < "title": "lineTileCollision", < "type": "action", < "name": "lineTileCollision", < "parameters": { < "collisionType": {"value": ["Null", "Block", "Dynamic"]}, < "offset": {"key": "toObject"}, < "position": {"key": "self"} < } < } < ] --- > "title": "sayToEntity", > "type": "action", > "name": "sayToEntity", > "parameters": { > "dialogType": {"value": "dialog.accuse"}, > "entity": {"key": "thiefTarget"}, > "tags": {"value": {}} > } 190,228c183,188 < "title": "sequence", < "type": "composite", < "name": "sequence", < "parameters": {}, < "children": [ < { < "title": "entityPosition", < "type": "action", < "name": "entityPosition", < "parameters": { < "entity": {"key": "thiefTarget"} < }, < "output": { < "position": "thiefPosition" < } < }, < { < "title": "distance", < "type": "action", < "name": "distance", < "parameters": { < "from": {"key": "self"}, < "to": {"key": "thiefPosition"} < }, < "output": { < "vector": "toThief" < } < }, < { < "title": "lineTileCollision", < "type": "action", < "name": "lineTileCollision", < "parameters": { < "collisionType": {"value": ["Null", "Block", "Dynamic"]}, < "offset": {"key": "toThief"}, < "position": {"key": "self"} < } < } < ] --- > "title": "timer", > "type": "action", > "name": "timer", > "parameters": { > "time": {"value": 2} > } 234,311c194,211 < "title": "selector", < "type": "composite", < "name": "selector", < "parameters": {}, < "children": [ < { < "title": "limiter", < "type": "decorator", < "name": "limiter", < "parameters": { < "limit": {"value": 2} < }, < "child": { < "title": "sequence", < "type": "composite", < "name": "sequence", < "parameters": {}, < "children": [ < { < "title": "sayToEntity", < "type": "action", < "name": "sayToEntity", < "parameters": { < "dialogType": {"value": "dialog.accuse"}, < "entity": {"key": "thiefTarget"}, < "tags": {"value": {}} < } < }, < { < "title": "timer", < "type": "action", < "name": "timer", < "parameters": { < "time": {"value": 2} < } < } < ] < } < }, < { < "title": "cooldown", < "type": "decorator", < "name": "cooldown", < "parameters": { < "cooldown": {"value": 1}, < "onFail": {"value": false}, < "onSuccess": {"value": true} < }, < "child": { < "title": "sequence", < "type": "composite", < "name": "sequence", < "parameters": {}, < "children": [ < { < "title": "sendEntityMessage", < "type": "action", < "name": "sendEntityMessage", < "parameters": { < "arguments": {"value": [2]}, < "entity": {"key": "thiefTarget"}, < "message": {"value": "raiseWantedLevel"} < } < }, < { < "title": "broadcastNotification", < "type": "action", < "name": "broadcastNotification", < "parameters": { < "entityTypes": {"value": ["npc"]}, < "position": {"key": "self"}, < "range": {"value": 40}, < "target": {"key": "reactTarget"}, < "type": {"value": "attackThief"} < } < } < ] < } --- > "title": "cooldown", > "type": "decorator", > "name": "cooldown", > "parameters": { > "cooldown": {"value": 1}, > "onFail": {"value": false}, > "onSuccess": {"value": true} > }, > "child": { > "title": "broadcastNotification", > "type": "action", > "name": "broadcastNotification", > "parameters": { > "entityTypes": {"value": ["npc"]}, > "position": {"key": "self"}, > "range": {"value": 40}, > "target": {"key": "reactTarget"}, > "type": {"value": "attackThief"} 313c213 < ] --- > } 317,318c217,218 < } < ] --- > ] > } dungeons\microdungeons\bounty\safehouse.dungeon 7c7 < "anchor" : [ "safehouse_1", "safehouse_2", "safehouse_3", "safehouse_4", "safehouse_5", "safehouse_6", "safehouse_7", "safehouse_8", "safehouse_9", "safehouse_10", "safehouse_11", "safehouse_12", "safehouse_13", "safehouse_14", "safehouse_15", "safehouse_16", "safehouse_17", "safehouse_18", "safehouse_19" ], --- > "anchor" : [ "safehouse_1", "safehouse_2", "safehouse_3", "safehouse_4", "safehouse_5", "safehouse_6", "safehouse_7", "safehouse_8", "safehouse_9", "safehouse_10", "safehouse_11", "safehouse_12", "safehouse_13", "safehouse_14", "safehouse_15", "safehouse_16", "safehouse_17", "safehouse_18", "safehouse_19", "safehouse_20" ], 146a147,153 > }, > { > "name" : "safehouse_20", > "rules" : [ > [ "maxSpawnCount", [1] ] > ], > "def" : [ "tmx", "safehouse_20.json" ] dungeons\microdungeons\bounty\safehouse_template.json [TMX file differences are left out for huge size.] interface\cockpit\cockpitutil.lua 207,209c207,209 < -- visitableParameters can return nil if the planet isn't generated or if the planet isn't visitable < -- planetParameters only returns nil if the planet isn't generated < -- use planetParameters to see if the planet is generated --- > -- visitableParameters can return nil if the planet isn't available (not generated/not fetched from master) > -- or if the planet isn't visitable, planetParameters only returns nil if the planet isn't available > -- use planetParameters to see if the planet is available 228,232c228 < while true do < local children = celestial.children(p) < if #children > 0 then < return children < end --- > while celestial.hasChildren(p) == nil do 234a231 > return celestial.children(p) interface\optionsmenu\optionsmenu.config 107c107 < "value" : "MULTIPLAYER VIA STEAM FRIENDS" --- > "value" : "MULTIPLAYER VIA STEAM / DISCORD" interface\scripted\bountyboard\bountyboardgui.config 83c83 < "position" : [210, 197], --- > "position" : [200, 197], 92c92 < "position" : [250, 197], --- > "position" : [230, 197], 97a98,106 > "lblCreditsHeader" : { > "type" : "label", > "zlevel" : 3, > "position" : [260, 197], > "hAnchor" : "left", > "vAnchor" : "bottom", > "color" : "#02033B", > "value" : "Credits" > }, 119c128 < "position" : [210, 192], --- > "position" : [200, 192], 128c137 < "position" : [250, 192], --- > "position" : [230, 192], 133a143,151 > "lblCreditsList" : { > "type" : "label", > "zlevel" : 3, > "position" : [260, 192], > "hAnchor" : "left", > "vAnchor" : "top", > "color" : "#F77EC8", > "value" : "Credits" > }, 223a242 > "missingAssignment" : "No peacekeper assignment found", 246c265,266 < "minor" : ["minorMonster", "minorGang"], --- > "minorPlanet" : ["minorMonster", "minorPlanet"], > "minor" : ["minorMonster", "minorPlanet", "minorSpace"], 261c281,282 < "minor" : ["minorMonster", "minorGang"], --- > "minorPlanet" : ["minorMonster", "minorPlanet"], > "minor" : ["minorMonster", "minorPlanet", "minorSpace"], 276c297,298 < "minor" : ["minorMonster", "minorGang"], --- > "minorPlanet" : ["minorMonster", "minorPlanet"], > "minor" : ["minorMonster", "minorPlanet", "minorSpace"], 291c313,314 < "minor" : ["minorMonster", "minorGang"], --- > "minorPlanet" : ["minorMonster", "minorPlanet"], > "minor" : ["minorMonster", "minorPlanet", "minorSpace"], 306c329,330 < "minor" : ["minorMonster", "minorGang"], --- > "minorPlanet" : ["minorMonster", "minorPlanet"], > "minor" : ["minorMonster", "minorPlanet", "minorSpace"], 308c332 < "capstone" : ["majorGang"] --- > "capstone" : ["majorCapstone"] 327c351,352 < "minor" : ["minorMonster", "minorGang"], --- > "minorPlanet" : ["minorMonster", "minorPlanet"], > "minor" : ["minorMonster", "minorPlanet", "minorSpace"], 347,352c372 < "captureOnly" : false, < "captureRewards" : { < "money" : 350, < "rank" : 5 < }, < "killRewards" : { --- > "rewards" : { 354,356c374,376 < "rank" : 5 < }, < "failureRankPenalty" : 2 --- > "rank" : 5, > "credits" : 1 > } 363c383 < "minorGeneric" : { --- > "minorPlanet" : { 369,371c389,390 < "questCategories" : ["planet", "anomaly", "ship"], < "captureOnly" : false, < "captureRewards" : { --- > "questCategories" : ["planet"], > "rewards" : { 373,377c392,393 < "rank" : 5 < }, < "killRewards" : { < "money" : 250, < "rank" : 5 --- > "rank" : 5, > "credits" : 1 379c395 < "failureRankPenalty" : 2 --- > "useGang" : true 386c402 < "minorGang" : { --- > "minorSpace" : { 392,394c408,409 < "questCategories" : ["planet", "anomaly", "ship"], < "captureOnly" : false, < "captureRewards" : { --- > "questCategories" : ["anomaly", "ship"], > "rewards" : { 396,400c411,412 < "rank" : 5 < }, < "killRewards" : { < "money" : 250, < "rank" : 5 --- > "rank" : 5, > "credits" : 1 402d413 < "failureRankPenalty" : 2, 417c428 < "preBountyQuest" : "pre_bounty_alive", --- > "preBountyQuest" : "pre_bounty", 420,421c431 < "captureOnly" : true, < "captureRewards" : { --- > "rewards" : { 423,427c433,434 < "rank" : 25 < }, < "killRewards" : { < "money" : 0, < "rank" : 0 --- > "rank" : 25, > "credits" : 3 429d435 < "failureRankPenalty" : 5, 437a444,465 > "majorCapstone" : { > "poster" : "major", > "questConfig" : { > "arcType" : "major", > "targetType" : "npc", > "preBountyQuest" : "pre_bounty", > "questCategories" : ["planet", "anomaly", "ship"], > "endStep" : "capture_bounty", > "stepCount" : [1, 1], > "rewards" : { > "money" : 1000, > "rank" : 25, > "credits" : 5 > }, > "useGang" : true > }, > "randomTags" : { > "wantedImage" : "wantedImage", > "captureType" : "capstoneText", > "posterColor" : "majorColor" > } > }, 448d475 < "captureOnly" : false, 450c477 < "captureRewards" : { --- > "rewards" : { 452,456c479,480 < "rank" : 25 < }, < "killRewards" : { < "money" : 500, < "rank" : 5 --- > "rank" : 25, > "credits" : 5 458d481 < "failureRankPenalty" : 0, 475d497 < "captureOnly" : false, 477c499 < "captureRewards" : { --- > "rewards" : { 479c501,502 < "rank" : 20 --- > "rank" : 20, > "credits" : 5 481,485d503 < "killRewards" : { < "money" : 500, < "rank" : 5 < }, < "failureRankPenalty" : 0, 502d519 < "captureOnly" : false, 504c521 < "captureRewards" : { --- > "rewards" : { 506,510c523,524 < "rank" : 20 < }, < "killRewards" : { < "money" : 500, < "rank" : 5 --- > "rank" : 20, > "credits" : 5 512d525 < "failureRankPenalty" : 0, 529d541 < "captureOnly" : false, 531c543 < "captureRewards" : { --- > "rewards" : { 533,537c545,546 < "rank" : 20 < }, < "killRewards" : { < "money" : 500, < "rank" : 5 --- > "rank" : 20, > "credits" : 5 539d547 < "failureRankPenalty" : 0, 565c573 < "preBountyQuest" : "pre_bounty_alive", --- > "preBountyQuest" : "pre_bounty", 567d574 < "captureOnly" : false, 570c577 < "captureRewards" : { --- > "rewards" : { 572c579,580 < "rank" : 20 --- > "rank" : 20, > "credits" : 10 574,578c582 < "killRewards" : { < "money" : 500, < "rank" : 5 < }, < "failureRankPenalty" : 0 --- > "useGang" : true interface\scripted\bountyboard\bountyboardgui.lua 35c35 < if compare(self.assignment.system, celestial.currentSystem()) then --- > if self.assignment and player.worldId() == self.bountyStation.worldId then 38d37 < generatePosterQuests() 54a54 > if not status then error(result) end 57,61d56 < if not status then < sb.logInfo("something went wrong loading posters: %s", result) < pane.dismiss() < return < end 125c120,121 < local systemName = celestialWrap.planetName(self.assignment.system) --- > if self.assignment then > local systemName = celestialWrap.planetName(self.assignment.system) 127,129c123,128 < local template = config.getParameter("assignmentFormat") < local assignmentString = template:gsub("", self.assignment.gang.name):gsub("", systemName) < widget.setText("lblAssignment", assignmentString) --- > local template = config.getParameter("assignmentFormat") > local assignmentString = template:gsub("", self.assignment.gang.name):gsub("", systemName) > widget.setText("lblAssignment", assignmentString) > else > widget.setText("lblAssignment", config.getParameter("missingAssignment")) > end 132,158c131,155 < function generatePosterQuests() < for _, p in pairs(self.posters) do < if not p.accepted then < local generator = BountyGenerator.new( < p.seed, < systemPosition(self.assignment.system), < self.bountyRanks[self.assignment.rank].systemTypes, < p.questConfig.questCategories, < p.questConfig.endStep) < < generator.stepCount = p.questConfig.stepCount or generator.stepCount < generator.level = self.assignment.rank + 1 < generator.systemCount = 3 < generator.preBountyQuest = p.questConfig.preBountyQuest < generator.captureOnly = p.questConfig.captureOnly or false < generator.captureRewards = scaleRewards(p.questConfig.captureRewards, self.assignment.rank) < generator.killRewards = scaleRewards(p.questConfig.killRewards, self.assignment.rank) < generator.failureRankPenalty = math.floor(p.questConfig.failureRankPenalty * self.bountyRanks[self.assignment.rank].rewardMultipliers.rank) < generator.targetPortrait = p.portrait < < if p.questConfig.arcType == "minor" then < if p.target.type == "monster" then < sb.logInfo("generating minor arc for monster bounty %s", p.target.name) < < p.arc = generator:generateMinorBounty(p.target) < else < sb.logInfo("generating minor arc for NPC bounty %s", p.target.name) --- > function generatePosterQuest(p, worlds) > if not p.accepted then > local generator = BountyGenerator.new( > p.seed, > systemPosition(self.assignment.system), > self.bountyRanks[self.assignment.rank].systemTypes, > p.questConfig.questCategories, > p.questConfig.endStep) > > generator.stepCount = p.questConfig.stepCount or generator.stepCount > generator.level = self.assignment.rank + 1 > generator.systemCount = 3 > generator.preBountyQuest = p.questConfig.preBountyQuest > generator.rewards = scaleRewards(p.questConfig.rewards, self.assignment.rank) > generator.targetPortrait = p.portrait > > sb.logInfo("Worlds left: %s", #worlds) > if p.questConfig.arcType == "minor" then > if p.target.type == "monster" then > sb.logInfo("generating minor arc for monster bounty %s", p.target.name) > > local w = table.remove(worlds, 1) > return generator:generateMinorBounty(p.target, {w}) > else > sb.logInfo("generating minor arc for NPC bounty %s", p.target.name) 160,166c157,158 < generator.stepCount = {1, 1} < p.arc = generator:generateBountyArc(p.target) < end < elseif p.questConfig.arcType == "major" then < sb.logInfo("generating major arc for bounty %s", p.target.name) < < p.arc = generator:generateBountyArc(p.target) --- > generator.stepCount = {1, 1} > return generator:generateBountyArc(p.target, worlds) 167a160,161 > elseif p.questConfig.arcType == "major" then > sb.logInfo("generating major arc for bounty %s", p.target.name) 169,172c163 < p.questIds = {} < for _, quest in pairs(p.arc.quests) do < table.insert(p.questIds, quest.questId) < end --- > return generator:generateBountyArc(p.target, worlds) 180c171,172 < rank = math.floor(rewards.rank * self.bountyRanks[rank].rewardMultipliers.rank) --- > rank = math.floor(rewards.rank * self.bountyRanks[rank].rewardMultipliers.rank), > credits = rewards.credits 190,193c182,185 < for i, questId in ipairs(p.questIds) do < sb.logInfo("accepted %s", questId) < -- if #p.questIds < 10 or i ~= #p.questIds then < -- world.sendEntityMessage(player.id(), questId..".complete") --- > for i, quest in ipairs(p.arc.quests) do > sb.logInfo("accepted %s", quest.questId) > -- if #p.arc.quests < 10 or i ~= #p.arc.quests then > -- world.sendEntityMessage(player.id(), quest.questId..".complete") 222,228c214,218 < < else < local currentSystem = celestial.currentSystem() < local systemParameters = celestialWrap.planetParameters(currentSystem) < if contains(rankInfo.systemTypes, systemParameters.typeName or "") then < -- if the current system is the right type, assign to current system < newSystem = currentSystem --- > elseif player.worldId() == self.bountyStation.worldId then > -- get assignment from the board, if any, or generate a new one in this system > local boardAssignment = util.await(world.sendEntityMessage(pane.sourceEntity(), "assignment")):result() > if boardAssignment then > self.assignment = boardAssignment 230,231c220 < -- otherwise, assing to a new nearby system < newSystem = findSystem(systemPosition(currentSystem), rankInfo.systemTypes) --- > newSystem = self.bountyStation.system 235,247c224,246 < local gang = rankInfo.gang or generateGang() < local generator = BountyGenerator.new(seed, systemPosition(newSystem), self.bountyRanks[rank].systemTypes) < local gangLeader = generator:generateBountyNpc(gang, gang.capstoneColor) < < self.lastAssignment = lastAssignment < self.assignment = { < rank = rank, < system = newSystem, < gang = gang, < gangLeader = gangLeader, < pointsToCapstone = rankInfo.pointsToCapstone, < final = finalAssignment < } --- > if newSystem then > local gang = rankInfo.gang or generateGang() > local generator = BountyGenerator.new(seed, systemPosition(newSystem), self.bountyRanks[rank].systemTypes) > local gangLeader = generator:generateBountyNpc(gang, gang.capstoneColor) > > self.lastAssignment = lastAssignment > if lastAssignment then > table.insert(self.assignmentLog, lastAssignment) > end > self.assignment = { > rank = rank, > system = newSystem, > gang = gang, > gangLeader = gangLeader, > pointsToCapstone = rankInfo.pointsToCapstone, > poolId = "standard", > final = finalAssignment, > } > self.bountyStation = { > system = newSystem, > worldId = nil > } > end 251a251,257 > function needFinalAssignment(rank) > return player.hasCompletedQuest("destroyruin") > and (not self.assignment or not self.assignment.final) > and not player.hasCompletedMission("missioncultist1") > and rank == #self.bountyRanks > end > 254c260 < self.posters = player.getProperty("bountyPosters") or {} --- > self.posters = player.getProperty("bountyPosters") or jarray() 256a263,264 > self.assignmentLog = player.getProperty("bountyAssignmentLog") or jarray() > self.bountyStation = player.getProperty("bountyStation") 259,264c267,282 < local assignmentRank = self.assignment and self.assignment.rank or playerRank < local needNewAssignment = forceNewAssignment or not self.assignment < local needFinalAssignment = player.hasCompletedQuest("destroyruin") < and (not self.assignment or not self.assignment.final) < and not player.hasCompletedMission("cultistmission1") < and assignmentRank == #self.bountyRanks --- > > -- if we're not in our assigned system, but we're in a system of the same rank, grab a new assignment from the board > local boardAssignment = util.await(world.sendEntityMessage(pane.sourceEntity(), "assignment")):result() > if self.assignment and boardAssignment and not compare(self.assignment.system, boardAssignment.system) then > local sameRank = self.assignment.rank == boardAssignment.rank > local xorFinal = self.assignment.final == boardAssignment.final > local previousAssignment = contains(self.assignmentLog, boardAssignment.system) > if sameRank and xorFinal and not previousAssignment then > self.assignment = boardAssignment > self.posters = {} > self.bountyStation = { > system = self.assignment.system, > worldId = player.worldId() > } > end > end 270c288 < local newMoney, newRankPoints = 0, 0 --- > local newMoney, newRankPoints, newCredits = 0, 0, 0 271a290,292 > local needNewAssignment = forceNewAssignment or not self.assignment > local hasActiveBounties = false > local assignmentRank = self.assignment and self.assignment.rank or getPlayerRank() 274c295,298 < for _, questId in pairs(p.questIds) do --- > for _, quest in pairs(p.arc.quests) do > local questId = quest.questId > world.sendEntityMessage(pane.sourceEntity(), "consumeQuest", questId) > 277,281c301,307 < table.insert(newEventDescriptions, {p.target.name, be.status, be.money or 0, be.rank or 0}) < if p.category == "capstone" and (be.status == "Killed" or be.status == "Captured") then < needNewAssignment = true < elseif p.category == "major" and (be.status == "Killed" or be.status == "Captured") and needFinalAssignment then < needNewAssignment = true --- > table.insert(newEventDescriptions, {p.target.name, be.status, be.money or 0, be.rank or 0, be.credits or 0}) > if be.status == "Captured" then > if p.category == "capstone" then > needNewAssignment = true > elseif p.category == "major" and needFinalAssignment(assignmentRank) then > needNewAssignment = true > end 282a309 > 284a312,318 > newCredits = newCredits + (be.credits or 0) > > if be.status == "Failed" then > -- failed bounties will be replaced with new ones off the board, > -- so there will be active bounties > return false > end 290,291c324,326 < < return false --- > else > -- unaccepted bounties on the board and bounties in the quest log both count as active > hasActiveBounties = true 293d327 < 300,301c334,336 < elseif newMoney < 0 then < player.consumeCurrency("money", math.abs(newMoney)) --- > end > if newCredits > 0 then > player.giveItem({"peacecredit", newCredits}) 323,325c358,366 < < if needNewAssignment then < getNewAssignment(needFinalAssignment) --- > > if not hasActiveBounties then > if needNewAssignment then > -- start a new assignment once the capstone is beaten > getNewAssignment(needFinalAssignment(getPlayerRank())) > else > -- no active bounties, grab new posters from the board > self.posters = {} > end 334c375 < if compare(self.assignment.system, celestial.currentSystem()) then --- > if player.worldId() == self.bountyStation.worldId then 340a382,383 > player.setProperty("bountyAssignmentLog", self.assignmentLog) > player.setProperty("bountyStation", self.bountyStation) 352d394 < questConfig = p.questConfig, 354,355c396,398 < questIds = p.questIds, < accepted = p.accepted --- > accepted = p.accepted, > arc = p.arc, > worlds = p.worlds, 358a402,406 > > -- save assignment to the bounty board so others can get the same assignment > if player.worldId() == self.bountyStation.worldId then > world.sendEntityMessage(pane.sourceEntity(), "setAssignment", self.assignment) > end 370,374c418,420 < if poster.questIds then < for _, questId in pairs(poster.questIds) do < if player.hasAcceptedQuest(questId) then < return true < end --- > for _, quest in pairs(poster.arc.quests) do > if player.hasAcceptedQuest(quest.questId) then > return true 382,386c428,430 < if poster.questIds then < for _, questId in pairs(poster.questIds) do < if player.hasActiveQuest(questId) then < return true < end --- > for _, quest in pairs(poster.arc.quests) do > if player.hasActiveQuest(quest.questId) then > return true 420c464 < function addPoster(bountyConfig, level, slot, category) --- > function makePoster(bountyConfig, level, slot, category) 510c554,568 < populateTempFields(poster) --- > return poster > end > > function bountyWorlds() > local worlds = util.await(world.sendEntityMessage(pane.sourceEntity(), "bountyWorlds")):result() > if #worlds == 0 then > local newWorlds = findWorlds(systemPosition(self.assignment.system), self.bountyRanks[self.assignment.rank].systemTypes, {"moon", "barren"}, 30, 4) > util.await(world.sendEntityMessage(pane.sourceEntity(), "setBountyWorlds", newWorlds)) > worlds = util.await(world.sendEntityMessage(pane.sourceEntity(), "bountyWorlds")):result() > end > shuffle(worlds) > > -- sort worlds by how many times they've been used in bounty quests > table.sort(worlds, function(l, r) return l[2] < r[2] end) > worlds = util.map(worlds, function(w) return w[1] end) 512c570 < table.insert(self.posters, poster) --- > return worlds 516,519c574,580 < local avail = availableSlots() < < while #avail > 0 do < local slot = table.remove(avail) --- > local maxPosters = 6 > > local poolId = self.assignment.poolId > local playerRank = getPlayerRank() > if #self.posters == 0 and playerRank > self.assignment.rank or (self.assignment.pointsToCapstone and self.assignment.pointsToCapstone <= 0) then > poolId = "capstone" > end 521,522c582,584 < local cats = currentCategories() < if cats.capstone then --- > local worlds = bountyWorlds() > while #self.posters < maxPosters do > if #self.posters > 0 and poolId == "capstone" then 524a587 > local boardPosters = util.await(world.sendEntityMessage(pane.sourceEntity(), "posterPool", poolId)):result() 526,533c589,597 < local targetCategory < local playerRank = getPlayerRank() < if cats.major then < targetCategory = "minor" < elseif playerRank > self.assignment.rank or (self.assignment.pointsToCapstone and self.assignment.pointsToCapstone <= 0) then < targetCategory = "capstone" < else < targetCategory = "major" --- > -- fill local available posters with posters from the corresponding slots on the board object > local localAvail = availableSlots(self.posters) > for _, slot in ipairs(localAvail) do > for _, p in ipairs(boardPosters) do > if compare(slot, p.slot) then > populateTempFields(p) > table.insert(self.posters, p) > end > end 536,540c600,635 < local rankInfo < if self.assignment.final then < rankInfo = self.finalAssignment < else < rankInfo = self.bountyRanks[self.assignment.rank] --- > -- break out if we have the max number of posters, or we have a capstone poster > local hasLocalCapstone = false > for _, p in ipairs(self.posters) do > if p.category == "capstone" then > hasLocalCapstone = true > end > end > if #self.posters >= maxPosters or (#self.posters > 0 and poolId == maxPool) then > break > end > > -- otherwise, generate new bounties to store on the board, and try again > local avail = availableSlots(boardPosters) > local majorWorlds = nil > local minorWorldPool = {} > > local boardHasCapstone = false > for _, p in ipairs(boardPosters) do > if p.category == "major" then > majorWorlds = p.worlds > elseif p.category == "capstone" then > boardHasCapstone = true > end > end > > -- major bounty worlds are used for minor bounties, > -- filter out all the used ones and leave available ones in a minor world pool > if majorWorlds then > minorWorldPool = majorWorlds > for _, p in ipairs(boardPosters) do > if p.category == "minor" then > for _,l in ipairs(p.worlds) do > minorWorldPool = util.filter(minorWorldPool, function(r) return not compare(l, r) end) > end > end > end 542,543d636 < local bountyType = util.randomChoice(rankInfo.bounties[targetCategory]) < local bc = self.bountyTypes[bountyType] 545c638,691 < addPoster(bc, 1, slot, targetCategory) --- > for _, slot in ipairs(avail) do > if boardHasCapstone then > break > end > > local targetCategory > if poolId == "capstone" then > -- capstone at the end of a tier > targetCategory = "capstone" > boardHasCapstone = true > elseif majorWorlds == nil then > -- otherwise, first make a major bounty > targetCategory = "major" > else > -- then generate minor bounties > if #minorWorldPool > 0 then > targetCategory = "minorPlanet" > else > targetCategory = "minor" > end > end > > local rankInfo > if self.assignment.final then > rankInfo = self.finalAssignment > else > rankInfo = self.bountyRanks[self.assignment.rank] > end > local bountyType = util.randomChoice(rankInfo.bounties[targetCategory]) > local bc = self.bountyTypes[bountyType] > local newPoster = makePoster(bc, 1, slot, targetCategory) > > local worlds = worlds > if majorWorlds then > if #minorWorldPool > 0 then > -- generate one minor bounty on each planet the major bounty uses > worlds = minorWorldPool > else > -- put the rest in the same systems as the major bounty visits > local majorSystems = util.filter(majorWorlds, function(w) return w.system end) > local worlds = util.filter(worlds, function(w) > return contains(majorSystems, w.system) > end) > end > end > newPoster.arc, newPoster.worlds = generatePosterQuest(newPoster, worlds) > > if targetCategory == "major" then > majorWorlds = shallowCopy(newPoster.worlds) > minorWorldPool = shallowCopy(majorWorlds) > end > > util.await(world.sendEntityMessage(pane.sourceEntity(), "addPoster", poolId, newPoster)) > end 551c697 < function availableSlots() --- > function availableSlots(posters) 557c703 < for _, p in pairs(self.posters) do --- > for _, p in pairs(posters) do 638a785 > world.sendEntityMessage(pane.sourceEntity(), "registerQuest", p.arc.quests[1].questId, p.worlds) 652a800 > creditsStr = string.format(e[5] > 0 and "%s+%d\n" or "%s%d\n", creditsStr, math.floor(e[5])) 657a806 > widget.setText("summaryOverlay.lblCreditsList", creditsStr) 700a850,857 > > > -- player.setProperty("bountyPoints", nil) > -- player.setProperty("lastBountyAssignment", nil) > -- player.setProperty("bountyAssignment", nil) > -- player.setProperty("bountyPosters", {}) > -- self.assignment.poolId = "standard" > -- player.setProperty("bountyAssignment", self.assignment) objects\outpost\bountyboard\bountyboard.lua 0a1,3 > require "/scripts/async.lua" > require "/scripts/bountygeneration.lua" > 3a7,61 > > storage.bountyWorlds = storage.bountyWorlds or {} > storage.questWorlds = storage.questWorlds or {} > storage.posterPools = storage.posterPools or {} > storage.assignment = storage.assignment or nil > message.setHandler("bountyWorlds", function(_, _) > return storage.bountyWorlds > end) > message.setHandler("setBountyWorlds", function(_, _, worlds) > storage.bountyWorlds = util.map(worlds, function(w) return {w, 0} end) > end) > message.setHandler("registerQuest", function(_, _, questId, worlds) > sb.logInfo("Registered %s with worlds %s", questId, worlds) > storage.questWorlds[questId] = worlds > > -- technically the world hasn't been visited yet, but we still want to > -- disincentivize picking it again for another quest > incrementQuestVisits(questId) > end) > message.setHandler("consumeQuest", function(_, _, questId) > -- each time a world is visited on a quest (the quest is completed by *anyone*) further disincentivize > -- using that world again > incrementQuestVisits(questId) > > -- remove the quest from the poster pools > for poolName, pool in pairs(storage.posterPools) do > storage.posterPools[poolName] = util.filter(pool, function(poster) > for _, quest in ipairs(poster.arc.quests) do > if quest.questId == questId then > sb.logInfo("Remove poster for quest %s", questId) > return false > end > end > return true > end) > end > end) > > message.setHandler("posterPool", function(_, _, poolId) > return storage.posterPools[poolId] or jarray() > end) > message.setHandler("addPoster", function(_, _, poolId, poster) > storage.posterPools[poolId] = storage.posterPools[poolId] or jarray() > local pool = storage.posterPools[poolId] > table.insert(pool, poster) > end) > > message.setHandler("assignment", function(_, _) > return storage.assignment > end) > message.setHandler("setAssignment", function(_, _, assignment) > if storage.assignment == nil then > storage.assignment = assignment > end > end) 16a75,86 > > function incrementQuestVisits(questId) > local worlds = storage.questWorlds[questId] > if worlds then > for _, w in ipairs(worlds) do > local bountyWorld = util.find(storage.bountyWorlds, function(bw) return compare(w, bw[1]) end) > if bountyWorld then > bountyWorld[2] = bountyWorld[2] + 1 > end > end > end > end \ No newline at end of file objects\outpost\bountyboard\bountyboard.object 28c28 < "scriptDelta" : 0, --- > "scriptDelta" : 10, objects\themed\egyptian\egyptianbed\egyptianbed.object 22c22 < "sitPosition" : [-10, 14.5], --- > "sitPosition" : [-5, 15], objects\themed\egyptian\egyptianpillar\egyptianpillar.object 27a28,29 > "direction" : "left", > "flipImages" : true, 31a34,37 > }, > { > "image" : "egyptianpillar.png:", > "imagePosition" : [-16, 0], 32a39,45 > "direction" : "right", > "frames" : 1, > "animationCycle" : 1.0, > > "spaceScan" : 0.1, > "anchors" : [ "bottom" ], > "collision" : "platform" objects\themed\egyptian\egyptianstatuette\egyptianstatuette.object 27a28,39 > "direction" : "left", > "flipImages" : true, > "spaceScan" : 0.1, > "anchors" : [ "bottom" ] > }, > { > "image" : "egyptianstatuette.png:", > > "imagePosition" : [-8, 0], > "frames" : 1, > "animationCycle" : 1.0, > "direction" : "right", parallax\surface\cyberspace.parallax 4a5 > 6,9c7,10 < "kind" : "cyberspace2", < "baseCount" : 1, < "offset" : [0, 130], < "parallax" : [6.0, 10.0] --- > "kind" : "cyberspace3", > "baseCount" : 6, > "offset" : [60, 215], > "parallax" : [2.0, 4.0] 10a12 > 12,15c14,17 < "kind" : "cyberspace2", < "baseCount" : 1, < "offset" : [0, 140], < "parallax" : [5.0, 8.0] --- > "kind" : "cyberspace3", > "baseCount" : 6, > "offset" : [40, 200], > "parallax" : [3.0, 5.0] 16a19 > 18,20c21,23 < "kind" : "cyberspace2", < "baseCount" : 1, < "offset" : [0, 150], --- > "kind" : "cyberspace3", > "baseCount" : 6, > "offset" : [20, 180], 23,34d25 < { < "kind" : "cyberspace2", < "baseCount" : 1, < "offset" : [0, 160], < "parallax" : [3.0, 4.0] < }, < { < "kind" : "cyberspace2", < "baseCount" : 1, < "offset" : [0, 170], < "parallax" : [2.0, 2.0] < }, 37,46c28,31 < "kind" : "cyberspace1", < "baseCount" : 1, < "offset" : [0, 0], < "parallax" : [3.0, 9.0] < }, < { < "kind" : "cyberspace1", < "baseCount" : 1, < "offset" : [0, 0], < "parallax" : [2.5, 7.0] --- > "kind" : "cyberspace4", > "baseCount" : 2, > "offset" : [0, 160], > "parallax" : [8.0, 10.0] 49,52c34,37 < "kind" : "cyberspace1", < "baseCount" : 1, < "offset" : [0, 0], < "parallax" : [2.0, 5.0] --- > "kind" : "cyberspace5", > "baseCount" : 3, > "offset" : [0, -20], > "parallax" : [70.0, 21.0] 55,58c40,43 < "kind" : "cyberspace1", < "baseCount" : 1, < "offset" : [0, 0], < "parallax" : [1.5, 3.0] --- > "kind" : "cyberspace2", > "baseCount" : 3, > "offset" : [0, 110], > "parallax" : [70.0, 36.0] quests\bounty\bounty.lua 76,81d75 < self.defaultAbandonMessages = { < "Yer jus' gonna give up? You oughta know by now that justice ain't easy. Sorry, but I've gotta dock you rank points for this.", < "Yer droppin' a case? You know how much paperwork this means I gotta do?! I'm afraid I gotta dock you for this, partner.", < "I jus' heard that you've dropped a case? There's no room in a Peacekeeper's day for givin' up! I'm deductin' from you, partner." < } < 150a145,147 > elseif quest.worldId() == nil then > -- the quest takes place on an unknown world, try to find a bounty manager for this world, potentially spawned by another player > self.findManager = coroutine.create(maybeLoadBountyManager) 203c200 < local capture = quest.parameters().capture --- > local rewards = quest.parameters().rewards 205,211c202 < local completionRewards = capture.rewards.kill < if storage.event["captured"] then < text = text.capture or text.default < completionRewards = capture.rewards.capture < else < text = text.default < end --- > text = text.capture or text.default 213c204 < modifyQuestEvents(storage.event["captured"] and "Captured" or "Killed", completionRewards.money, completionRewards.rank) --- > modifyQuestEvents("Captured", rewards.money, rewards.rank, rewards.credits) 216c207 < tags.bountyPoints = completionRewards.rank --- > tags.bountyPoints = rewards.rank 226c217 < world.sendEntityMessage(quest.questArcDescriptor().stagehandUniqueId, "playerComplete", quest.questId()) --- > world.sendEntityMessage(quest.questArcDescriptor().stagehandUniqueId, "playerComplete", player.uniqueId(), quest.questId()) 240,252c231 < local penalty = quest.parameters().failurePenalty < < if abandoned then < local tags = util.generateTextTags(quest.parameters().text.tags) < tags.failureRankPenalty = penalty.rank < text = sb.replaceTags(util.randomFromList(self.defaultAbandonMessages), tags) < < player.radioMessage(radioMessage(text, "angry")) < < modifyQuestEvents("Abandoned", -(penalty.money or 0), -(penalty.rank or 0)) < else < modifyQuestEvents("Failed", -(penalty.money or 0), -(penalty.rank or 0)) < end --- > modifyQuestEvents("Failed", 0, 0, 0) 255c234 < world.sendEntityMessage(quest.questArcDescriptor().stagehandUniqueId, "playerFailed", quest.questId()) --- > world.sendEntityMessage(quest.questArcDescriptor().stagehandUniqueId, "playerFailed", player.uniqueId(), quest.questId()) 304c283 < world.sendEntityMessage(quest.questArcDescriptor().stagehandUniqueId, "playerStarted", quest.questId()) --- > world.sendEntityMessage(quest.questArcDescriptor().stagehandUniqueId, "playerStarted", player.uniqueId(), quest.questId()) 309d287 < source = entity.uniqueId(), 310a289 > worldId = player.worldId(), 312d290 < worldId = player.worldId() 318a297,311 > function maybeLoadBountyManager() > local stagehandId = quest.questArcDescriptor().stagehandUniqueId > while true do > local findManager = util.await(world.findUniqueEntity(stagehandId)) > if findManager:succeeded() then > if util.await(world.sendEntityMessage(stagehandId, "involvesQuest", quest.questId())):result() then > world.sendEntityMessage(stagehandId, "playerStarted", player.uniqueId(), quest.questId()) > return findManager:result() > end > end > > util.wait(3.0) > end > end > 334a328 > 398c392 < function modifyQuestEvents(status, money, rank) --- > function modifyQuestEvents(status, money, rank, credits) 403a398 > thisQuestEvents.credits = (thisQuestEvents.credits or 0) + credits quests\bounty\bountyassignment.lua 134c134,139 < quest.setWorldId(celestialWrap.objectWarpActionWorld(stationUuid)) --- > local stationWorld = celestialWrap.objectWarpActionWorld(stationUuid) > quest.setWorldId(stationWorld) > player.setProperty("bountyStation", { > system = storage.bountySystem, > worldId = stationWorld > }) quests\bounty\bounty_gen.lua 1a2 > require "/interface/cockpit/cockpitutil.lua" 7c8,12 < "bribe" --- > "scared", > "crazy", > "deceptive", > "tricky", > "bribe", 8a14,18 > local worlds = {} > local currentWorld > for i = 1, 10 do > table.insert(worlds, worldIdCoordinate(player.worldId())) > end 11,12c21,22 < local generator, target = questGenerator(behaviorName, "capture_bounty", 1) < local arc = generator:generateBountyArc(target) --- > local generator, target = questGenerator(behaviorName, "capture_bounty", 3) > local arc, used = generator:generateBountyArc(target, worlds) quests\bounty\bounty_mission.questtemplate 63,65d62 < "capture" : [ < "Mighty fine job takin' on , partner! Justice truly has been served this day.\n\nCome by the Peacekeeper Station to collect your rewards!" < ], 66a64 > "Mighty fine job takin' on , partner! Justice truly has been served this day.\n\nCome by the Peacekeeper Station to collect your rewards!", 71,72c69,70 < "killed" : [ < "Unfortunately, violence doesn't solve everything. The assignment was to capture ALIVE so that they could be properly tried and sentenced according to due process. You've failed to serve justice in this matter." --- > "escape" : [ > "--TODO-- You let them go!? How is that supposed to make the universe a safer place?" quests\bounty\capture_bounty.questtemplate 60,62d59 < "capture" : [ < "Mighty fine job takin' on ! The universe is vast, but the arm of the law is long!\n\nCome by the Peacekeeper Station to collect your rewards!" < ], 64c61,62 < "You took on ? an' came out on top! The universe is vast, but the arm of the law is long.\n\nCome by the Peacekeeper Station to collect your rewards!" --- > "You took on ? an' came out on top! The universe is vast, but the arm of the law is long.\n\nCome by the Peacekeeper Station to collect your rewards!", > "Mighty fine job takin' on ! The universe is vast, but the arm of the law is long!\n\nCome by the Peacekeeper Station to collect your rewards!" 68,69c66,67 < "killed" : [ < "Unfortunately, violence doesn't solve everything. The assignment was to capture ALIVE so that they could be properly tried and sentenced according to due process. You've failed to serve justice in this matter." --- > "default" : [ > "--TODO-- You let them go!? How is that supposed to make the universe a safer place?" quests\bounty\capture_ship_bounty.questtemplate 67,68c67,68 < "killed" : [ < "Unfortunately, violence doesn't solve everything. The assignment was to capture ALIVE so that they could be properly tried and sentenced according to due process. You've failed to serve justice in this matter." --- > "default" : [ > "--TODO-- You let them go!? How is that supposed to make the universe a safer place?" quests\bounty\capture_space_bounty.questtemplate 59,61d58 < "capture" : [ < "Nice work goin' to space and capturin' ! You're a real galactic hero.\n\nCome by the Peacekeeper Station to collect your reward!" < ], 67,68c64,65 < "killed" : [ < "Unfortunately, violence doesn't solve everything. The assignment was to capture ALIVE so that they could be properly tried and sentenced according to due process. You've failed to serve justice in this matter." --- > "default" : [ > "--TODO-- You let them go!? How is that supposed to make the universe a safer place?" quests\bounty\clue_items.config 555c555 < "noteText" : "Lackey,\n\nThe goods are now .\nMake sure you aren't being followed.\n\nRegards,\n" --- > "noteText" : "Hey Crew,\n\nYou're never gonna believe this!\nI TOTALLY fooled a Peacekeeper earlier by pretending to be someone else!\nThey were well and truly pranked.\n\nUntil next time,\n" 557c557 < "message" : "Always follow the paper trail, partner! It looks like is your next stop on this case." --- > "message" : "Dangit, they fooled us AND wrote a newsletter about it! Better head back to your previous location, partner!" 561c561 < "noteText" : "'s To-do List\n\nª Buy blowtorch\n\nª Rob a few banks\n\nª Hide out on \n\nª Wait for the heat to die down" --- > "noteText" : "Peacekeeper,\nIf you're reading this, I want you to know that the helpful stranger earlier...\n\nIt was ME, !\n\nNever forget the moment that you were utterly bamboozled by my cunning.\n«" 563c563 < "message" : "It looks like these criminals are too organised for their own good! You should head on over to !" --- > "message" : "This varmint is gonna pay for their mockery! Head back to your previous location and catch this crook for good!" 567c567 < "noteText" : "Dear ,\n\nI'm sorry I forgot to bring the goods to you earlier.\nI'm on my way to meet you .\nPlease don't be mad.\n\n±" --- > "noteText" : "Dear Diary,\n\nEarlier I pretended I was someone else and a local Peacekeeper was totally fooled.\nThey were totally looking for me, too.\nI am officially a master of disguise and can blend into the shadows at will." 569c569 < "message" : "Conflict within a team will always be its downfall - let this be a lesson, partner! You should head on over to ." --- > "message" : "This vagabond tricked us! Too bad that they wrote down their dirty work on a convenient piece of paper. Better head over to your previous location, partner!" 573c573 < "noteText" : "Dear Diary,\n\nToday I helped by taking the loot to .\nI hope I'll get a raise soon!\nI should probably hide this page for now in case someone finds it." --- > "noteText" : "Peacekeeper,\nI expect that you should discover this note in time.\n\nI want you to know it was me.\nThe civilian who assisted you earlier? \nIt was I, !\n\nWhilst you still have a good chance of catching me, I don't want you ever to forget the name...\nª ª" 575c575 < "message" : "If you're gonna dispose of written evidence, you gotta do a better job than hidin' it! You should head on over to ." --- > "message" : "This rogue thinks they've humiliated us, don't they? Head back to your previous location and catch this varmint once and for all!" quests\bounty\clue_scans.config 4,5c4,5 < "description" : "i can extract a DNA sample from this handprint", < "message" : "this DNA signature matches a henchman of , they were last seen on " --- > "description" : "^#60b8ea;[SIGNATURE TRACE COMPLETE: ^yellow;UPLOADING CO-ORDINATES^#60b8ea;]", > "message" : "Your ^orange;Matter Manipulator^white; has a trace on one of 's goons! They are currently on !" 8,9c8,9 < "description" : "this handprint seems to match the target", < "message" : "this DNA signature matches ! we've picked up their signature from a planet in . Head to " --- > "description" : "^#60b8ea;[DNA SAMPLE CONFIRMED: ^yellow;UPLOADING CO-ORDINATES^#60b8ea;]", > "message" : "Your ^orange;Matter Manipulator^white; has a lock on ! Their signature trae leads to a planet in . Head on over to !" 12,13c12,13 < "description" : "this handprint seems to match the target", < "message" : "this DNA signature matches a henchman of , they've been known to hang out on a station in " --- > "description" : "^#60b8ea;[DNA SAMPLE CONFIRMED: ^yellow;UPLOADING CO-ORDINATES^#60b8ea;]", > "message" : "Your ^orange;Matter Manipulator^white; has a trce on one of 's henchmen! They currently appear to be on a station in !" 16,17c16,35 < "description" : "this handprint seems to match the target", < "message" : "this DNA signature matches ! we've detected a matching signature at a small station in " --- > "description" : "^#60b8ea;[SIGNATURE TRACE COMPLETE: ^yellow;UPLOADING CO-ORDINATES^#60b8ea;]", > "message" : "Your ^orange;Matter Manipulator^white; has traced 's signature! It seems to lead to a remote station in !" > } > }, > "scanclue2" : { > "planetClue" : { > "description" : "^#60b8ea;[SIGNATURE TRACE COMPLETE: ^yellow;UPLOADING CO-ORDINATES^#60b8ea;]", > "message" : "Your ^orange;Matter Manipulator^white; has a trace on one of 's goons! They are currently on !" > }, > "planetBounty" : { > "description" : "^#60b8ea;[DNA SAMPLE CONFIRMED: ^yellow;UPLOADING CO-ORDINATES^#60b8ea;]", > "message" : "Your ^orange;Matter Manipulator^white; has a lock on ! Their signature trae leads to a planet in . Head on over to !" > }, > "spaceClue" : { > "description" : "^#60b8ea;[DNA SAMPLE CONFIRMED: ^yellow;UPLOADING CO-ORDINATES^#60b8ea;]", > "message" : "Your ^orange;Matter Manipulator^white; has a trce on one of 's henchmen! They currently appear to be on a station in !" > }, > "spaceBounty" : { > "description" : "^#60b8ea;[SIGNATURE TRACE COMPLETE: ^yellow;UPLOADING CO-ORDINATES^#60b8ea;]", > "message" : "Your ^orange;Matter Manipulator^white; has traced 's signature! It seems to lead to a remote station in !" quests\bounty\find_clue_item.questtemplate 56d55 < quests\bounty\generator.config 363,368c363 < "questParameters" : { < "systemSpawns" : { < "type" : "json", < "spaceObject" : "bountyanomaly" < } < }, --- > "questParameters" : {}, 370d364 < "diversion", 377a372,375 > "systemSpawn" : { > "objectType" : "bountyanomaly" > }, > 443,448c441 < "questParameters" : { < "systemSpawns" : { < "type" : "json", < "spaceObject" : "bountyanomaly" < } < }, --- > "questParameters" : {}, 455a449,452 > "systemSpawn" : { > "objectType" : "bountyanomaly" > }, > 577,582c574 < "questParameters" : { < "systemSpawns" : { < "type" : "json", < "spaceObject" : "bountyanomaly" < } < }, --- > "questParameters" : {}, 589a582,585 > > "systemSpawn" : { > "objectType" : "bountyanomaly" > }, 654,659c650 < "questParameters" : { < "systemSpawns" : { < "type" : "json", < "spaceObject" : "bountyanomaly" < } < }, --- > "questParameters" : {}, 670a662,665 > "systemSpawn" : { > "objectType" : "bountyanomaly" > }, > 723,728c718 < "questParameters" : { < "systemSpawns" : { < "type" : "json", < "spaceObject" : "bountyanomaly" < } < }, --- > "questParameters" : {}, 741a732,735 > "systemSpawn" : { > "objectType" : "bountyanomaly" > }, > 759,770c753 < "questParameters" : { < "bounty" : { < "type" : "npcType", < "species" : "human", < "typeName" : "bandit", < "parameters" : {} < }, < "systemSpawns" : { < "type" : "json", < "spaceObject" : "bountyship" < } < }, --- > "questParameters" : {}, 776a760,763 > "systemSpawn" : { > "objectType" : "bountyship" > }, > 819a807,811 > "coordinate" : { > "type" : "previous", > "questParameter" : "world" > }, > quests\bounty\kill_bounty_monster.questtemplate 57,59d56 < "capture" : [ < "Mighty fine job takin' on ! True justice has been served on this day.\n\nCome by the Peacekeeper office next time you're on the Outpost to collect your rewards!" < ], 65,66c62,63 < "killed" : [ < "Unfortunately, violence doesn't solve everything. The assignment was to capture ALIVE so that they could be properly tried and sentenced according to due process. You've failed to serve justice in this matter." --- > "default" : [ > "--TODO-- You let them go!? How is that supposed to make the universe a safer place?" quests\bounty\pre_bounty_capstone.questtemplate 24c24 < "It's time to take down the ^orange;^reset; for good. We've picked up the trail of their leader, ^orange;^reset;, .\n\nMake sure you're well prepared for this one - their base is sure to be heavily fortified.\n\n- REWARDS -\nCapture: ^yellow;^reset; pixels, + rank" --- > "It's time to take down the ^orange;^reset; for good. We've picked up the trail of their leader, ^orange;^reset;, .\n\nMake sure you're well prepared for this one - their base is sure to be heavily fortified.\n\n- REWARDS -\nCapture: ^yellow;^reset; pixels, + rank" quests\bounty\pre_bounty_minor_monster.questtemplate 24,26c24,26 < "We've had reports of a creature wreaking havoc ! The locals have taken to calling it '^orange;^reset;' and they're too afraid to leave their homes. Can you travel there and take care of it?\n\n- REWARDS -\nKill: ^yellow;^reset; pixels, + rank", < "We've had reports of a creature running loose . Some darn fool named it '^orange;^reset;' and now nobody has the heart to put it down. You'd better get over there and put a stop to this monstrosity before anyone else gets hurt.\n\n- REWARDS -\nKill: ^yellow;^reset; pixels, + rank", < "We've been recieving word of a local legend . A strangely-coloured creature called ^orange;^reset; has been known to harass locals for some time, and they're darn sick of it. Can you go and sort out this mess?\n\n- REWARDS -\nKill: ^yellow;^reset; pixels, + rank" --- > "We've had reports of a creature wreaking havoc ! The locals have taken to calling it '^orange;^reset;' and they're too afraid to leave their homes. Can you travel there and take care of it?\n\n- REWARDS -\nKill: ^yellow;^reset; pixels, + rank", > "We've had reports of a creature running loose . Some darn fool named it '^orange;^reset;' and now nobody has the heart to put it down. You'd better get over there and put a stop to this monstrosity before anyone else gets hurt.\n\n- REWARDS -\nKill: ^yellow;^reset; pixels, + rank", > "We've been recieving word of a local legend . A strangely-coloured creature called ^orange;^reset; has been known to harass locals for some time, and they're darn sick of it. Can you go and sort out this mess?\n\n- REWARDS -\nKill: ^yellow;^reset; pixels, + rank" quests\bounty\pre_bounty_minor_npc.questtemplate 24c24 < "We need you to apprehend a small-time crook known as ^orange;^reset; .\n\nThey're pretty small-time, so they ain't gonna cause you too much of a problem.\n\n- REWARDS -\nCapture: ^yellow;^reset; pixels, + rank" --- > "We need you to apprehend a small-time crook known as ^orange;^reset; .\n\nThey're pretty small-time, so they ain't gonna cause you too much of a problem.\n\n- REWARDS -\nCapture: ^yellow;^reset; pixels, + rank" quests\bounty\scan_planets.questtemplate 16c16 < "completionText" : "You killed the bounty", --- > "completionText" : "", quests\bounty\stages.lua 16a17,20 > function onQuestWorld() > return player.worldId() == quest.worldId() and player.serverUuid() == quest.serverUuid() > end > 79c83 < while not storage.spawned["bounty"] and player.worldId() == quest.worldId() do --- > while not storage.spawned["bounty"] and onQuestWorld() do 93d96 < local captureOnly = quest.parameters().capture.captureOnly 95c98 < while not storage.killed["bounty"] and player.worldId() == quest.worldId() do --- > while not storage.killed["bounty"] and onQuestWorld() do 119,128c122 < < if storage.event["captured"] or not captureOnly then < return nextStage() < else < local text = config.getParameter("generatedText.failure.killed") < local tags = util.generateTextTags(quest.parameters().text.tags) < text = sb.replaceTags(util.randomFromList(text), tags) < quest.setFailureText(text) < return quest.fail() < end --- > return nextStage() 142c136 < while not storage.spawned["clue"] and player.worldId() == quest.worldId() do --- > while not storage.spawned["clue"] and onQuestWorld() do 197c191 < while not storage.spawned["clue"] and player.worldId() == quest.worldId() do --- > while not storage.spawned["clue"] and onQuestWorld() do 258c252 < while not storage.spawned["clue"] and player.worldId() == quest.worldId() do --- > while not storage.spawned["clue"] and onQuestWorld() do 303c297 < while (not storage.spawned["clue"] or not storage.scanIds["clue"]) and player.worldId() == quest.worldId() do --- > while (not storage.spawned["clue"] or not storage.scanIds["clue"]) and onQuestWorld() do 340c334 < local spawn = quest.parameters().systemSpawns.spaceObject --- > local systemSpawn = quest.parameters().systemSpawn 352a347 > 366,371c361,370 < if inSystem and (not questLocation or not questLocation.location or not celestial.objectPosition(questLocation.location[2])) then < local uuid = sb.makeUuid() < celestial.systemSpawnObject(spawn, nil, uuid) < < quest.setLocation({system = system.location, location = {"object", uuid}}) < quest.setWorldId(celestialWrap.objectWarpActionWorld(uuid)) --- > if inSystem and celestial.objectPosition(systemSpawn.uuid) == nil then > celestial.systemSpawnObject(systemSpawn.objectType, nil, systemSpawn.uuid) > while celestial.objectPosition(systemSpawn.uuid) == nil do > coroutine.yield() > end > end > quest.setLocation({system = system.location, location = {"object", systemSpawn.uuid}}) > local warpActionWorld = celestial.objectWarpActionWorld(systemSpawn.uuid) > if warpActionWorld then > quest.setWorldId(warpActionWorld) 376d374 < 401,403c399,401 < local planetName = function(p) < while celestial.planetName(p) == nil do < coroutine.yield() --- > planets = util.filter(planets, function(p) > if celestialWrap.planetParameters(p).worldType == "Terrestrial" then > return true 405,410c403,406 < return celestial.planetName(p) < end < < local planetParameters = function(p) < while celestial.planetParameters(p) == nil do < coroutine.yield() --- > for _, m in ipairs(celestialWrap.children(p)) do > if celestialWrap.planetParameters(m).worldType == "Terrestrial" then > return true > end 412,416c408 < return celestial.planetParameters(p) < end < < planets = util.filter(planets, function(p) < return planetParameters(p).worldType == "Terrestrial" --- > return false 421c413,415 < local _, planetIndex = util.find(planets, function(p) return compare(p, world) end) --- > local planetWorld = copy(world) > planetWorld.satellite = 0 > local _, planetIndex = util.find(planets, function(p) return compare(p, planetWorld) end) 423c417 < return {string.format(config.getParameter("objectives.scanPlanetsStage.scan"), planetName(p)), false} --- > return {string.format(config.getParameter("objectives.scanPlanetsStage.scan"), celestialWrap.planetName(p)), false} 449c443 < text = string.format(config.getParameter("scanMessage"), planetName(v)) --- > text = string.format(config.getParameter("scanMessage"), celestialWrap.planetName(v)) 492c486 < while not storage.spawned["teleport"] and player.worldId() == quest.worldId() do --- > while not storage.spawned["teleport"] and onQuestWorld() do recipes\peacekeeperstore\mechbodypeacekeeper.recipe 3c3 < { "item" : "peacecredit", "count" : 6 } --- > { "item" : "peacecredit", "count" : 30 } recipes\peacekeeperstore\peacekeeperchest.recipe 3c3 < { "item" : "peacecredit", "count" : 1 } --- > { "item" : "peacecredit", "count" : 3 } recipes\peacekeeperstore\peacekeeperhead.recipe 3c3 < { "item" : "peacecredit", "count" : 2 } --- > { "item" : "peacecredit", "count" : 5 } recipes\peacekeeperstore\peacekeeperpants.recipe 3c3 < { "item" : "peacecredit", "count" : 1 } --- > { "item" : "peacecredit", "count" : 3 } scripts\bountygeneration.lua 23a24,59 > function findWorlds(position, systemTypes, exclude, minWorlds, minSystems) > minCount = minCount or 1 > local size = {25, 25} > local region = rect.withCenter(position, size) > > local systems = {} > local worlds = {} > local maybeAddWorld = function(w) > local parameters = celestialWrap.planetParameters(w) > if parameters.worldType ~= "Terrestrial" then > return > end > local visitable = celestialWrap.visitableParameters(w) > if visitable and not contains(exclude, visitable.typeName) then > table.insert(worlds, w) > end > end > > while #worlds < minWorlds or #systems < minSystems do > systems = celestialWrap.scanSystems(region, systemTypes) > worlds = {} > for _,s in ipairs(systems) do > for _, planet in ipairs(celestialWrap.children(s)) do > maybeAddWorld(planet) > for _, moon in ipairs(celestialWrap.children(planet)) do > maybeAddWorld(moon) > end > end > end > size = vec2.mul(size, math.sqrt(2)) > region = rect.withCenter(position, size) > end > > return worlds, systems > end > 82,87c118 < self.captureOnly = true < self.captureRewards = { < money = 0, < rank = 0 < } < self.killRewards = { --- > self.rewards = { 89c120,121 < rank = 0 --- > rank = 0, > credits = 0 91d122 < self.failureRankPenalty = 0 444c475 < function BountyGenerator:processSteps(steps, bounty) --- > function BountyGenerator:processSteps(steps, bounty, planetPool) 448a480 > local systemSpawns = {} 450a483,484 > local usedPlanets = {} -- keep track of used planets to return with steps > 457a492 > systemSpawns[step.questId] = stepConfig.systemSpawn or nil 545c580,584 < local world = self:findWorld({exclude}) --- > local world = table.remove(planetPool, 1) > if world == nil then > error("Not enough worlds in the planet pool") > end > table.insert(usedPlanets, world) 552,556c591,597 < local exclude < if lastQuestId and coordinateConfigs[lastQuestId].type == "system" then < exclude = coordinateSystem(coordinates[lastQuestId]) < end < local system = findSystem(self.position, self.systemTypes, {exclude}, self.config.systemCount, self.rand) --- > local world = table.remove(planetPool, 1) > if world == nil then > error("Not enough worlds in the planet pool to use for system") > end > local system = copy(world) > system.planet = 0 > system.satellite = 0 808a850,858 > > local systemSpawn = systemSpawns[step.questId] > if systemSpawn then > step.questParameters.systemSpawn = { > type = "json", > objectType = systemSpawn.objectType, > uuid = sb.makeUuid(), > } > end 905c955 < return quests --- > return quests, usedPlanets 909c959 < function BountyGenerator:questArc(steps, bountyTarget) --- > function BountyGenerator:questArc(steps, bountyTarget, planetPool) 919c969 < capture = { --- > rewards = { 921,925c971,973 < captureOnly = self.captureOnly, < rewards = { < capture = self.captureRewards, < kill = self.killRewards < } --- > money = self.rewards.money, > rank = self.rewards.rank, > credits = self.rewards.credits 930,940d977 < for _, step in pairs(steps) do < table.insert(step.merge, { < questParameters = { < failurePenalty = { < type = "json", < rank = self.failureRankPenalty < } < } < }) < end < 942c979,980 < arc.quests = self:processSteps(steps, bountyTarget) --- > local usedPlanets > arc.quests, usedPlanets = self:processSteps(steps, bountyTarget, planetPool) 958,959c996 < captureRewards = self.captureRewards, < killRewards = self.killRewards --- > rewards = self.rewards 970c1007 < return arc --- > return arc, usedPlanets 973c1010 < function BountyGenerator:generateBountyArc(bountyTarget) --- > function BountyGenerator:generateBountyArc(bountyTarget, planetPool) 991c1028 < return self:questArc(steps, bountyTarget) --- > return self:questArc(steps, bountyTarget, planetPool) 994c1031 < function BountyGenerator:generateMinorBounty(bountyTarget) --- > function BountyGenerator:generateMinorBounty(bountyTarget, planetPool) 1005,1040c1042,1043 < return self:questArc(steps, bountyTarget) < end < < function BountyGenerator:findWorld(exclude) < exclude = exclude or {} < local planets = {} < while #planets == 0 do < local targetSystem = findSystem(self.position, self.systemTypes, nil, self.config.systemCount, self.rand) < < planets = celestialWrap.children(targetSystem) < < planets = util.filter(planets, function(p) < if contains(exclude, p) then < return false < end < local parameters = celestialWrap.planetParameters(p) < if parameters.worldType ~= "Terrestrial" then < return false < end < return celestialWrap.visitableParameters(p) ~= nil < end) < end < local planet = util.randomFromList(planets, self.rand) < < if self.debug then < local coordinate = worldIdCoordinate(player.worldId()) < if coordinate then < local shipLocation = {"coordinate", coordinate} < if shipLocation[1] == "coordinate" then < return shipLocation[2] < end < end < end < < return planet < end --- > return self:questArc(steps, bountyTarget, planetPool) > end \ No newline at end of file stagehands\bountymanager.lua 10,11d9 < self.source = config.getParameter("source") < self.npcType = config.getParameter("npcType") 13a12,18 > -- when spawned in an instance world we can't deduce the world ID from quest parameters > self.questId = config.getParameter("questId") > > self.quests = {} > for _, quest in ipairs(self.questArc.quests) do > self.quests[quest.questId] = quest > end 15d19 < storage.tileProtection = storage.tileProtection or {} 21,24c25,27 < message.setHandler("playerStarted", function(_, _, questId) < if questId ~= storage.questId then < playerStarted(questId) < end --- > message.setHandler("involvesQuest", function(_, _, questId) return isQuestOnWorld(questId) end) > message.setHandler("playerStarted", function(_, _, playerId, questId) > playerStarted(playerId, questId) 26,27c29,30 < message.setHandler("playerCompleted", function(_, _, questId) < playerCompleted(questId) --- > message.setHandler("playerCompleted", function(_, _, playerId, questId) > playerCompleted(playerId, questId) 29,30c32,33 < message.setHandler("playerFailed", function(_, _, questId) < playerFailed(questId) --- > message.setHandler("playerFailed", function(_, _, playerId, questId) > playerFailed(playerId, questId) 33,34c36,39 < world.callScriptedEntity(entityId, "object.say", questStorage().dialog[world.entityUniqueId(entityId)]) < self.outbox:sendMessage(self.source, questId..".participantEvent", world.entityUniqueId(entityId), "objectInteracted") --- > world.callScriptedEntity(entityId, "object.say", storage.questStorage[questId].dialog[world.entityUniqueId(entityId)]) > for _, playerId in ipairs(storage.questStorage[questId].players) do > self.outbox:sendMessage(playerId, questId..".participantEvent", world.entityUniqueId(entityId), "objectInteracted") > end 37c42,44 < self.outbox:sendMessage(self.source, questId.."keypadUnlocked", uniqueId, questId) --- > for _, playerId in ipairs(storage.questStorage[questId].players) do > self.outbox:sendMessage(playerId, questId.."keypadUnlocked", uniqueId, questId) > end 48a56 > storage.tileProtection = storage.tileProtection or {} 50a59 > players = jarray(), 73,87d81 < < startSpawners() < end < < function questStorage(questId) < questId = questId or storage.questId < if questId then < return storage.questStorage[questId] < else < return { < dialog = {}, < spawned = {}, < locations = {} < } < end 93,123c87,92 < for spawnName, spawn in pairs(self.spawners) do < self.keepAlive = true < local status, result, position = coroutine.resume(spawn.coroutine, spawn.config, spawnName) < if not status then < error(result) < end < if result then < self.outbox:sendMessage(self.source, storage.questId.."entitySpawned", spawnName, result) < < if spawn.config.type == "npc" and not spawn.config.multiple then < local role = { < turnInQuests = {}, < offerQuest = false, < participateIn = { < [storage.questId] = true < }, < stateDeltas = { < [storage.questId] = {} < }, < behaviorOverrides = spawn.config.behaviorOverrides or {} < } < < self.outbox:sendMessage(result, "reserve", entity.uniqueId(), self.questArc, role) < local questDesc = util.find(self.questArc.quests, function(q) return q.questId == storage.questId end) < self.outbox:sendMessage(result, "playerStarted", entity.uniqueId(), self.source, storage.questId, questDesc.parameters) < end < < if spawn.config.questId then < if not questStorage(spawn.config.questId).spawned[spawn.config.name] then < questStorage(spawn.config.questId).spawned[spawn.config.name] = result < end --- > for questId, questSpawners in pairs(self.spawners) do > for spawnName, spawn in pairs(questSpawners) do > self.keepAlive = true > local status, result, position = coroutine.resume(spawn.coroutine, spawn.config) > if not status then > error(result) 124a94,97 > if result then > for _, playerId in ipairs(storage.questStorage[questId].players) do > self.outbox:sendMessage(playerId, questId.."entitySpawned", spawnName, result) > end 126,135c99,139 < if spawn.config.type == "monster" then < local uuid = questStorage(spawn.config.questId).spawned[spawn.config.name] < table.insert(self.tasks, coroutine.create(function() return trackEntity(uuid) end)) < end < < self.spawners[spawnName] = nil < elseif position then < if not spawn.pendingPosition or world.magnitude(position, spawn.pendingPosition) > 40 then < spawn.pendingPosition = position < self.outbox:unreliableMessage(self.source, storage.questId.."entityPending", spawnName, spawn.pendingPosition) --- > if spawn.config.type == "npc" and not spawn.config.multiple then > local role = { > turnInQuests = {}, > offerQuest = false, > participateIn = { > [questId] = true > }, > stateDeltas = { > [questId] = {} > }, > behaviorOverrides = spawn.config.behaviorOverrides or {} > } > > self.outbox:sendMessage(result, "reserve", entity.uniqueId(), self.questArc, role) > local questDesc = self.quests[questId] > for _,playerId in ipairs(storage.questStorage[questId].players) do > self.outbox:sendMessage(result, "playerStarted", entity.uniqueId(), playerId, questId, questDesc.parameters) > end > end > > local questStorage = storage.questStorage[spawn.config.questId] > if spawn.config.questId then > if not questStorage.spawned[spawn.config.name] then > questStorage.spawned[spawn.config.name] = result > end > end > > if spawn.config.type == "monster" then > local uuid = questStorage.spawned[spawn.config.name] > table.insert(self.tasks, coroutine.create(function() return trackEntity(questId, uuid) end)) > end > > self.spawners[questId][spawnName] = nil > elseif position then > if not spawn.pendingPosition or world.magnitude(position, spawn.pendingPosition) > 40 then > spawn.pendingPosition = position > > for _,playerId in ipairs(storage.questStorage[questId].players) do > self.outbox:unreliableMessage(playerId, questId.."entityPending", spawnName, spawn.pendingPosition) > end > end 156,158c160,166 < for name, spawnResult in pairs(questStorage().spawned) do < if type("spawnResult") == "string" and spawnResult == uniqueId then < self.outbox:sendMessage(self.source, storage.questId.."entityDied", name, uniqueId) --- > for questId, questStore in pairs(storage.questStorage) do > for name, spawnResult in pairs(questStore.spawned) do > if type("spawnResult") == "string" and spawnResult == uniqueId then > for _,playerId in ipairs(storage.questStorage[questId].players) do > self.outbox:sendMessage(playerId, questId.."entityDied", name, uniqueId) > end > end 176c184 < function playerStarted(questId) --- > function clearUnusedQuestSpawns() 178,181c186,194 < for _, spawnResult in pairs(questStorage().spawned) do < if type(spawnResult) == "string" then < self.outbox:sendMessage(spawnResult, "playerCompleted", entity.uniqueId(), self.source, storage.questId) < self.outbox:sendMessage(spawnResult, "unreserve", entity.uniqueId(), self.questArc) --- > for questId, questStore in pairs(storage.questStorage) do > if #questStore.players == 0 then > for _, spawnResult in pairs(questStore.spawned) do > if type(spawnResult) == "string" then > self.outbox:sendMessage(spawnResult, "playerCompleted", entity.uniqueId(), playerId, questId) > self.outbox:sendMessage(spawnResult, "unreserve", entity.uniqueId(), self.questArc) > end > end > questStore.spawned = {} 183a197 > end 185,233c199,240 < -- set new quest active and start spawning entities < storage.questId = questId < storage.quest = util.find(self.questArc.quests, function(q) return q.questId == questId end) < startSpawners() < end < < function startSpawners() < if storage.questId then < self.spawners = {} < local addScanObjectLocation = nil < for spawnName, spawnConfig in pairs(self.questSpawns[storage.questId]) do < spawnConfig.questId = storage.questId < spawnConfig.name = spawnName < < while spawnConfig.type == "otherQuest" do < local q = util.find(self.questArc.quests, function(q) return q.questId == spawnConfig.quest end) < local spawnsParameter = q.parameters.spawns < < local newConfig = copy(spawnsParameter.spawns[spawnConfig.spawn]) < newConfig.questId = spawnConfig.quest < newConfig.name = spawnConfig.spawn < newConfig.location = spawnConfig.location or newConfig.location < spawnConfig = newConfig < end < < local locationConfig = self.questLocations[storage.questId][spawnConfig.location] < local stepWorld = storage.quest.parameters.world and coordinateWorldId(storage.quest.parameters.world.coordinate) < local worldId = locationConfig.worldId or stepWorld or self.worldId < if worldId == self.worldId then < local spawner < if questStorage(spawnConfig.questId).spawned[spawnConfig.name] then < spawner = coroutine.create(function() < return questStorage(spawnConfig.questId).spawned[spawnConfig.name] < end) < else < if spawnConfig.type == "npc" then < if not spawnConfig.multiple then < -- spawn for a single clue or bounty npc, add scan objects < addScanObjectLocation = spawnConfig.location < end < spawner = coroutine.create(spawnNpc) < elseif spawnConfig.type == "monster" then < spawner = coroutine.create(spawnMonster) < elseif spawnConfig.type == "item" then < -- spawn for an item clue, add scan objects < addScanObjectLocation = spawnConfig.location < spawner = coroutine.create(spawnItem) < elseif spawnConfig.type == "object" then < -- spawn for an object clue, add scan objects --- > function playerStarted(playerId, questId) > if contains(storage.questStorage[questId].players, playerId) then > return > end > table.insert(storage.questStorage[questId].players, playerId) > clearUnusedQuestSpawns() > > startSpawners(questId) > end > > function startSpawners(questId) > self.spawners[questId] = {} > local addScanObjectLocation = nil > for spawnName, spawnConfig in pairs(self.questSpawns[questId]) do > spawnConfig.questId = questId > spawnConfig.name = spawnName > > while spawnConfig.type == "otherQuest" do > local q = util.find(self.questArc.quests, function(q) return q.questId == spawnConfig.quest end) > local spawnsParameter = q.parameters.spawns > > local newConfig = copy(spawnsParameter.spawns[spawnConfig.spawn]) > newConfig.questId = spawnConfig.quest > newConfig.name = spawnConfig.spawn > newConfig.location = spawnConfig.location or newConfig.location > spawnConfig = newConfig > end > > local locationConfig = self.questLocations[questId][spawnConfig.location] > local stepWorld = self.quests[questId].parameters.world and coordinateWorldId(self.quests[questId].parameters.world.coordinate) > local worldId = locationConfig.worldId or stepWorld or self.worldId > if worldId == self.worldId then > local spawner > local questStorage = storage.questStorage[questId] > if questStorage.spawned[spawnConfig.name] then > spawner = coroutine.create(function() > return questStorage.spawned[spawnConfig.name] > end) > else > if spawnConfig.type == "npc" then > if not spawnConfig.multiple then > -- spawn for a single clue or bounty npc, add scan objects 235,245d241 < spawner = coroutine.create(spawnObject) < elseif spawnConfig.type == "keypad" then < spawner = coroutine.create(setKeypadCode) < elseif spawnConfig.type == "stagehand" then < spawner = coroutine.create(spawnStagehand) < elseif spawnConfig.type == "scan" then < hasScanClue = true < spawnConfig.clue = true < spawner = coroutine.create(spawnScanObject) < else < error(string.format("No spawner available for spawn type %s", spawnConfig.type)) 246a243,263 > spawner = coroutine.create(spawnNpc) > elseif spawnConfig.type == "monster" then > spawner = coroutine.create(spawnMonster) > elseif spawnConfig.type == "item" then > -- spawn for an item clue, add scan objects > addScanObjectLocation = spawnConfig.location > spawner = coroutine.create(spawnItem) > elseif spawnConfig.type == "object" then > -- spawn for an object clue, add scan objects > addScanObjectLocation = spawnConfig.location > spawner = coroutine.create(spawnObject) > elseif spawnConfig.type == "keypad" then > spawner = coroutine.create(setKeypadCode) > elseif spawnConfig.type == "stagehand" then > spawner = coroutine.create(spawnStagehand) > elseif spawnConfig.type == "scan" then > hasScanClue = true > spawnConfig.clue = true > spawner = coroutine.create(spawnScanObject) > else > error(string.format("No spawner available for spawn type %s", spawnConfig.type)) 248,252d264 < < self.spawners[spawnName] = { < config = spawnConfig, < coroutine = spawner < } 254,263c266,269 < end < < -- spawn non-clue scan objects at locations that have another clue < if addScanObjectLocation ~= nil then < self.spawners["inertScans"] = { < config = { < location = addScanObjectLocation, < clue = false, < }, < coroutine = coroutine.create(spawnScanObject) --- > > self.spawners[questId][spawnName] = { > config = spawnConfig, > coroutine = spawner 266a273,285 > > -- spawn non-clue scan objects at locations that have another clue > if addScanObjectLocation ~= nil then > self.spawners[questId]["inertScans"] = { > config = { > location = addScanObjectLocation, > questId = questId, > name = "inertScans", > clue = false, > }, > coroutine = coroutine.create(spawnScanObject) > } > end 274,276c293,308 < function playerCompleted(questId) < local quests = currentPlanetQuests() < if quests[#quests] == questId then --- > function anyActivePlayers() > for _, questStore in pairs(storage.questStorage) do > if #questStore.players > 0 then > return true > end > end > return false > end > > function playerCompleted(playerId, questId) > storage.questStorage[questId].players = util.filter(storage.questStorage[questId].players, function(p) > return p ~= playerId > end) > clearUnusedQuestSpawns() > > if lastQuestOnWorld(questId) and not anyActivePlayers() then 281,282c313,317 < function playerFailed() < for _, spawnResult in pairs(questStorage().spawned) do --- > function playerFailed(playerId, questId) > storage.questStorage[questId].players = util.filter(storage.questStorage[questId].players, function(p) > return p ~= playerId > end) > for _, spawnResult in pairs(storage.questStorage[questId].spawned) do 284,285c319,322 < self.outbox:unreliableMessage(spawnResult, "playerFailed", entity.uniqueId(), self.source, storage.questId) < self.outbox:unreliableMessage(spawnResult, "unreserve", entity.uniqueId(), self.questArc) --- > self.outbox:unreliableMessage(spawnResult, "playerFailed", entity.uniqueId(), playerId, questId) > if #storage.questStorage[questId].players == 0 then > self.outbox:unreliableMessage(spawnResult, "unreserve", entity.uniqueId(), self.questArc) > end 289,302c326,327 < die() < end < < function currentPlanetQuests() < local questPlanet = storage.quest.parameters.world < if questPlanet then < return util.map(util.filter(self.questArc.quests, function(q) < local world = q.parameters.world < return compare(world and world.coordinate, questPlanet.coordinate) < end), function(q) < return q.questId < end) < else < return nil --- > if not anyActivePlayers() then > die() 306,308c331,341 < function keepAlive() < while storage.quest == nil do < coroutine.yield() --- > function isQuestOnWorld(questId) > if questId == self.questId then > return true > end > for _, quest in pairs(self.questArc.quests) do > if quest.questId == questId then > local world = quest.parameters.world > if world and coordinateWorldId(world.coordinate) == self.worldId then > return true > end > end 309a343,344 > return false > end 311,320c346,358 < local quests < local lastQuest < local questPlanet = storage.quest.parameters.world < local worldQuests = currentPlanetQuests() < if worldQuests then < quests = self.questArc.quests < lastQuest = worldQuests[#worldQuests] < else < quests = {storage.quest} < lastQuest = storage.questId --- > function lastQuestOnWorld(questId) > local checkWorld = nil > for _, quest in pairs(self.questArc.quests) do > if checkWorld then > local world = quest.parameters.world > if compare(checkWorld.coordinate, world and world.coordinate) then > -- there is a quest ahead that uses the same world, this is not the last quest on this world > return false > end > end > if quest.questId == questId then > checkWorld = quest.parameters.world > end 321a360,361 > return true > end 322a363 > function keepAlive() 325,340c366,374 < local source = world.loadUniqueEntity(self.source) < if source and world.entityExists(source) then < for _,q in ipairs(quests) do < local promise = world.sendEntityMessage(self.source, q.questId.."keepAlive") < while not promise:finished() do < coroutine.yield() < end < < if promise:succeeded() then < onQuest = true < break < end < < -- the player is either not on the arc, or is past the last quest that takes place on this world < if q.questId == lastQuest then < break --- > for questId, questStore in pairs(storage.questStorage) do > for _,playerId in ipairs(questStore.players) do > local entityId = world.loadUniqueEntity(playerId) > if entityId ~= nil and world.entityExists(entityId) then > local onQuest = util.await(world.sendEntityMessage(playerId, questId.."keepAlive")):succeeded() > if not onQuest then > -- the player is on the world but no longer on this quest, the player must have abandoned or failed the quest > playerFailed(playerId, questId) > end 343,345d376 < else < -- if the player is not on the world, assume they're still on the bounty arc < onQuest = true 348,352c379 < if not onQuest then < playerFailed(storage.questId) < end < < util.wait(0.5) --- > util.wait(1.0) 380c407 < function setTileProtection(questId, dungeonId) --- > function setTileProtection(dungeonId) 387c414 < function removeTileProtection() --- > function removeTileProtection(questId) 389c416 < for _,dungeonId in pairs(storage.tileProtection) do --- > for _, dungeonId in ipairs(storage.tileProtection) do 391a419 > storage.tileProtection = {} 435c463 < setTileProtection(storage.questId, dungeonId) --- > setTileProtection(dungeonId) 442,444c470,472 < function getLocationPositions(locationName) < if questStorage().locations[locationName] then < return shuffled(questStorage().locations[locationName]) --- > function getLocationPositions(questId, locationName) > if storage.questStorage[questId].locations[locationName] then > return shuffled(storage.questStorage[questId].locations[locationName]) 451c479 < local locationConfig = self.questLocations[storage.questId][locationName] --- > local locationConfig = self.questLocations[questId][locationName] 460c488 < questStorage().locations[locationName] = {result} --- > storage.questStorage[questId].locations[locationName] = {result} 479c507 < questStorage().locations[locationName] = waypoints[locationConfig.stagehand] --- > storage.questStorage[questId].locations[locationName] = waypoints[locationConfig.stagehand] 484c512 < questStorage().locations[locationName] = {findStagehand:result()} --- > storage.questStorage[questId].locations[locationName] = {findStagehand:result()} 493c521 < while not questStorage().locations[locationName] do --- > while not storage.questStorage[questId].locations[locationName] do 497c525 < return shuffled(questStorage().locations[locationName]) --- > return shuffled(storage.questStorage[questId].locations[locationName]) 501c529 < local positions = getLocationPositions(spawnConfig.location) --- > local positions = getLocationPositions(spawnConfig.questId, spawnConfig.location) 508c536 < local positions = getLocationPositions(spawnConfig.location) --- > local positions = getLocationPositions(spawnConfig.questId, spawnConfig.location) 546c574 < function trackEntity(uniqueId) --- > function trackEntity(questId, uniqueId) 565c593 < local positions = getLocationPositions(spawnConfig.location) --- > local positions = getLocationPositions(spawnConfig.questId, spawnConfig.location) 608c636 < local positions = getLocationPositions(spawnConfig.location) --- > local positions = getLocationPositions(spawnConfig.questId, spawnConfig.location) 657c685 < local positions = getLocationPositions(spawnConfig.location) --- > local positions = getLocationPositions(spawnConfig.questId, spawnConfig.location) 703,705c731,735 < local tags = util.generateTextTags(storage.quest.parameters.text.tags) < questStorage().dialog[uniqueId] = sb.replaceTags(clueConfig.dialog, tags) < self.outbox:sendMessage(self.source, storage.questId.."setCompleteMessage", sb.replaceTags(clueConfig.message, tags)) --- > local tags = util.generateTextTags(self.quests[spawnConfig.questId].parameters.text.tags) > storage.questStorage[spawnConfig.questId].dialog[uniqueId] = sb.replaceTags(clueConfig.dialog, tags) > for _,playerId in ipairs(storage.questStorage[spawnConfig.questId].players) do > self.outbox:sendMessage(playerId, spawnConfig.questId.."setCompleteMessage", sb.replaceTags(clueConfig.message, tags)) > end 711,712c741,742 < function spawnScanObject(spawnConfig, spawnName) < local positions = getLocationPositions(spawnConfig.location) --- > function spawnScanObject(spawnConfig) > local positions = getLocationPositions(spawnConfig.questId, spawnConfig.location) 753,755c783,787 < < local tags = util.generateTextTags(storage.quest.parameters.text.tags) < self.outbox:sendMessage(self.source, storage.questId.."setCompleteMessage", sb.replaceTags(clueConfig.message, tags)) --- > > local tags = util.generateTextTags(self.quests[spawnConfig.questId].parameters.text.tags) > for _,playerId in ipairs(storage.questStorage[spawnConfig.questId].players) do > self.outbox:sendMessage(playerId, spawnConfig.questId.."setCompleteMessage", sb.replaceTags(clueConfig.message, tags)) > end 774,775c806,808 < self.outbox:sendMessage(self.source, storage.questId.."scanIds", spawnName, uuids) < --- > for _, playerId in ipairs(storage.questStorage[spawnConfig.questId].players) do > self.outbox:sendMessage(playerId, spawnConfig.questId.."scanIds", spawnConfig.name, uuids) > end 782c815 < local positions = getLocationPositions(spawnConfig.location) --- > local positions = getLocationPositions(spawnConfig.questId, spawnConfig.location) tenants\other\peacekeepertenant.tenant 14c14 < "species": "human", --- > "species": ["human", "apex", "avian", "floran", "glitch", "hylotl"], tiles\materials\slopedpolygon.material 7,10c7,10 < "description" : " It's virtual!", < "glitchDescription" : " Observant. It's virtual!", < "floranDescription" : " It feellsss electric.", < "shortdescription" : "Hull Panel", --- > "description" : "A curious material made of \"Hard Light\".", > "glitchDescription" : "Observant. This matter is made of \"Hard Light\".", > "floranDescription" : "It feelsss electric.", > "shortdescription" : "Polygon Panel", 21c21 < "multiColored" : false, --- > "multiColored" : true, tilesets\packed\materials.json 655c655 < "//description" : " It's virtual!", --- > "//description" : "A curious material made of \"Hard Light\".", 657c657 < "//shortdescription" : "Hull Panel", --- > "//shortdescription" : "Polygon Panel", tilesets\packed\supports.json 7c7 < "tilecount" : 36, --- > "tilecount" : 37, 165a166,171 > "36" : { > "//description" : "Tough pressurised platform for potentially pressured environments!", > "//name" : "polygonplatform", > "//shortdescription" : "Pressurised Platform", > "material" : "polygonplatform" > }, 288a295,297 > }, > "36" : { > "image" : "./../../../../tiled/packed/materials/polygonplatform.png" tilesets\packed\objects-by-category\decorative.json 4124c4124 < "//description" : "-todo-", --- > "//description" : "A decorative crest with colourful wings", 4160c4160 < "//description" : "-todo-", --- > "//description" : "A large computer server rack.", 4178c4178 < "//description" : "-todo-", --- > "//description" : "A hanging board with papers tacked on it.", 4186c4186 < "//description" : "-todo-", --- > "//description" : "A bright red stapler.", 4197c4197 < "//shortdescription" : "Outpost Peacekeeper Poster", --- > "//shortdescription" : "Peacekeeper Poster", tilesets\packed\objects-by-category\furniture.json 2429c2429 < "//description" : "-todo-", --- > "//description" : "A lavishly decorated bed designed for lounging.", 2438c2438 < "//description" : "-todo-", --- > "//description" : "A heavy table made from wood and gold.", 2456c2456 < "//description" : "-todo-", --- > "//description" : "An ornamental pillar decorated with gold.", 2464c2464 < "//description" : "-todo-", --- > "//description" : "A golden chair decorated with symbols.", 2473c2473 < "//description" : "-todo-", --- > "//description" : "A delicately decorated statue of a small creature.", 2481c2481 < "//description" : "-todo-", --- > "//description" : "A wooden table with metal legs.", tilesets\packed\objects-by-category\light.json 3494c3494 < "//description" : "-todo-", --- > "//description" : "A small lamp with an adjustable head.", tilesets\packed\objects-by-category\storage.json 1284c1284 < "//description" : "-todo-", --- > "//description" : "A simple desk made from wood and metal.", tilesets\packed\objects-by-category\wire.json 1181c1181 < "//description" : "-todo-", --- > "//description" : "An early-model personal computer from Earth.", tilesets\packed\objects-by-colonytag\commerce.json 500c500 < "//description" : "-todo-", --- > "//description" : "A heavy table made from wood and gold.", 517c517 < "//description" : "-todo-", --- > "//description" : "An ornamental pillar decorated with gold.", 549c549 < "//description" : "-todo-", --- > "//description" : "A wooden table with metal legs.", 558c558 < "//description" : "-todo-", --- > "//description" : "A simple desk made from wood and metal.", tilesets\packed\objects-by-colonytag\egyptian.json 11c11 < "//description" : "-todo-", --- > "//description" : "A lavishly decorated bed designed for lounging.", 20c20 < "//description" : "-todo-", --- > "//description" : "A heavy table made from wood and gold.", 29c29 < "//description" : "-todo-", --- > "//description" : "A decorative crest with colourful wings", 38c38 < "//description" : "-todo-", --- > "//description" : "An ornamental pillar decorated with gold.", 46c46 < "//description" : "-todo-", --- > "//description" : "A golden chair decorated with symbols.", 55c55 < "//description" : "-todo-", --- > "//description" : "A delicately decorated statue of a small creature.", tilesets\packed\objects-by-colonytag\electronic.json 7c7 < "tilecount" : 131, --- > "tilecount" : 132, 300c300 < "//description" : "-todo-", --- > "//description" : "An early-model personal computer from Earth.", 325a326,334 > "131" : { > "//description" : "A large computer server rack.", > "//name" : "officeserver", > "//shortdescription" : "Office Server", > "imagePositionX" : "-16", > "imagePositionY" : "0", > "object" : "officeserver", > "tilesetDirection" : "right" > }, 1248a1258,1260 > }, > "131" : { > "image" : "../../../../../tiled/packed/objects/officeserver.png" tilesets\packed\objects-by-colonytag\light.json 2614c2614 < "//description" : "-todo-", --- > "//description" : "A small lamp with an adjustable head.", tilesets\packed\objects-by-colonytag\misc.json 600c600 < "//description" : "-todo-", --- > "//description" : "A lavishly decorated bed designed for lounging.", 609c609 < "//description" : "-todo-", --- > "//description" : "A golden chair decorated with symbols.", 636c636 < "//description" : "-todo-", --- > "//description" : "A bright red stapler.", tilesets\packed\objects-by-colonytag\office.json 11c11 < "//description" : "-todo-", --- > "//description" : "An early-model personal computer from Earth.", 20c20 < "//description" : "-todo-", --- > "//description" : "A wooden table with metal legs.", 29c29 < "//description" : "-todo-", --- > "//description" : "A large computer server rack.", 47c47 < "//description" : "-todo-", --- > "//description" : "A simple desk made from wood and metal.", 74c74 < "//description" : "-todo-", --- > "//description" : "A small lamp with an adjustable head.", 83c83 < "//description" : "-todo-", --- > "//description" : "A hanging board with papers tacked on it.", 91c91 < "//description" : "-todo-", --- > "//description" : "A bright red stapler.", tilesets\packed\objects-by-colonytag\peacekeeper.json 109c109 < "//shortdescription" : "Outpost Peacekeeper Poster", --- > "//shortdescription" : "Peacekeeper Poster", tilesets\packed\objects-by-colonytag\pretty.json 3648c3648 < "//description" : "-todo-", --- > "//description" : "A decorative crest with colourful wings", 3657c3657 < "//description" : "-todo-", --- > "//description" : "A delicately decorated statue of a small creature.", 3665c3665 < "//description" : "-todo-", --- > "//description" : "A hanging board with papers tacked on it.", tilesets\packed\objects-by-colonytag\storage.json 1657c1657 < "//description" : "-todo-", --- > "//description" : "A simple desk made from wood and metal.", tilesets\packed\objects-by-colonytag\wired.json 781c781 < "//description" : "-todo-", --- > "//description" : "An early-model personal computer from Earth.", tilesets\packed\objects-by-race\generic.json 8886c8886 < "//description" : "-todo-", --- > "//description" : "A lavishly decorated bed designed for lounging.", 8895c8895 < "//description" : "-todo-", --- > "//description" : "A heavy table made from wood and gold.", 8904c8904 < "//description" : "-todo-", --- > "//description" : "A decorative crest with colourful wings", 8921c8921 < "//description" : "-todo-", --- > "//description" : "An ornamental pillar decorated with gold.", 8929c8929 < "//description" : "-todo-", --- > "//description" : "A golden chair decorated with symbols.", 8956c8956 < "//description" : "-todo-", --- > "//description" : "A delicately decorated statue of a small creature.", 9060c9060 < "//description" : "-todo-", --- > "//description" : "An early-model personal computer from Earth.", 9069c9069 < "//description" : "-todo-", --- > "//description" : "A wooden table with metal legs.", 9078c9078 < "//description" : "-todo-", --- > "//description" : "A large computer server rack.", 9096c9096 < "//description" : "-todo-", --- > "//description" : "A simple desk made from wood and metal.", 9114c9114 < "//description" : "-todo-", --- > "//description" : "A small lamp with an adjustable head.", 9123c9123 < "//description" : "-todo-", --- > "//description" : "A hanging board with papers tacked on it.", 9131c9131 < "//description" : "-todo-", --- > "//description" : "A bright red stapler.", 9151c9151 < "//shortdescription" : "Outpost Peacekeeper Poster", --- > "//shortdescription" : "Peacekeeper Poster", tilesets\packed\objects-by-type\container.json 1767c1767 < "//description" : "-todo-", --- > "//description" : "A simple desk made from wood and metal.", tilesets\packed\objects-by-type\loungeable.json 1759c1759 < "//description" : "-todo-", --- > "//description" : "A lavishly decorated bed designed for lounging.", 1777c1777 < "//description" : "-todo-", --- > "//description" : "A golden chair decorated with symbols.", treasure\space.treasurepools 367a368,382 > "industrialLargeCargoCanister" : [ > [1, { > "pool" : [ > {"weight" : 0.33, "item" : [ "liquidfuel", 400]}, > {"weight" : 0.33, "item" : [ "liquidfuel", 500]}, > {"weight" : 0.33, "item" : [ "liquidfuel", 550]} > ], > "poolRounds" : [ > [0.50, 1], > [0.30, 2], > [0.20, 3] > ], > "allowDuplication" : true > }] > ],