FILES ----- debug.macros dungeons\microdungeons\bounty\boatvault_1.json dungeons\microdungeons\bounty\boatvault_2.json dungeons\microdungeons\bounty\boatvault_3.json dungeons\microdungeons\bounty\boat_1.json dungeons\microdungeons\bounty\boat_2.json dungeons\microdungeons\bounty\boat_3.json dungeons\microdungeons\bounty\boat_4.json dungeons\microdungeons\bounty\boat_5.json dungeons\microdungeons\bounty\boat_6.json dungeons\microdungeons\bounty\boat_7.json dungeons\microdungeons\bounty\boat_8.json dungeons\microdungeons\bounty\boat_9.json [NEW] dungeons\microdungeons\bounty\fueldepot.dungeon [NEW] dungeons\microdungeons\bounty\fueldepot_1.json [NEW] dungeons\microdungeons\bounty\fueldepot_2.json [NEW] dungeons\microdungeons\bounty\fueldepot_3.json [NEW] dungeons\microdungeons\bounty\undergroundfueldepot.dungeon [NEW] dungeons\microdungeons\bounty\undergroundfueldepot_1.json [NEW] dungeons\microdungeons\bounty\undergroundfueldepot_2.json [NEW] dungeons\microdungeons\bounty\undergroundfueldepot_3.json dungeons\missions\cultistmissions\cultistmission1.json dungeons\other\bountylair\bountylair1.json dungeons\other\bountylair\bountylair2.json dungeons\other\bountylair\bountylair3.json dungeons\other\bountylair\bountylair4.json dungeons\other\cyberspace\cyberdungeon_middle_1.json dungeons\other\cyberspace\cyberdungeon_middle_2.json dungeons\other\cyberspace\start_cyberspace.json [NEW] interface\quests\questlog\currentworld.png interface\scripted\bountyboard\bountyboardgui.config interface\scripted\bountyboard\bountyboardgui.lua [NEW] interface\warping\returnteleporter.config interface\windowconfig\questlog.config monsters\boss\swansong\noxcapture.lua monsters\boss\swansong\swansong.lua monsters\boss\swansong\swansong.monstertype monsters\flyers\cosmicintruder\cosmicintruder.monstertype [NEW] objects\teleporter\exitteleporter\returnteleporter.object objects\wired\secretdoor\secretdoor.lua quests\bounty\bounty.lua quests\bounty\bounty_gen.lua quests\bounty\dungeons.config scripts\bountygeneration.lua stats\effects\maxprotection\loweredprotection.statuseffect tilesets\packed\miscellaneous.json tilesets\packed\objects-by-category\actionfigure.json tilesets\packed\objects-by-category\decorative.json tilesets\packed\objects-by-category\furniture.json tilesets\packed\objects-by-category\generic.json tilesets\packed\objects-by-category\light.json tilesets\packed\objects-by-category\teleporter.json tilesets\packed\objects-by-colonytag\commerce.json tilesets\packed\objects-by-colonytag\cultist.json tilesets\packed\objects-by-colonytag\egyptian.json tilesets\packed\objects-by-colonytag\light.json tilesets\packed\objects-by-colonytag\misc.json tilesets\packed\objects-by-colonytag\pretty.json tilesets\packed\objects-by-race\generic.json tilesets\packed\objects-by-race\human.json DIFFS ----- debug.macros 48a49 > "/spawnitem manipulatormodule 60", dungeons\microdungeons\bounty\boatvault_1.json [TMX file differences are left out for huge size.] dungeons\microdungeons\bounty\boatvault_2.json [TMX file differences are left out for huge size.] dungeons\microdungeons\bounty\boatvault_3.json [TMX file differences are left out for huge size.] dungeons\microdungeons\bounty\boat_1.json [TMX file differences are left out for huge size.] dungeons\microdungeons\bounty\boat_2.json [TMX file differences are left out for huge size.] dungeons\microdungeons\bounty\boat_3.json [TMX file differences are left out for huge size.] dungeons\microdungeons\bounty\boat_4.json [TMX file differences are left out for huge size.] dungeons\microdungeons\bounty\boat_5.json [TMX file differences are left out for huge size.] dungeons\microdungeons\bounty\boat_6.json [TMX file differences are left out for huge size.] dungeons\microdungeons\bounty\boat_7.json [TMX file differences are left out for huge size.] dungeons\microdungeons\bounty\boat_8.json [TMX file differences are left out for huge size.] dungeons\microdungeons\bounty\boat_9.json [TMX file differences are left out for huge size.] dungeons\missions\cultistmissions\cultistmission1.json [TMX file differences are left out for huge size.] dungeons\other\bountylair\bountylair1.json [TMX file differences are left out for huge size.] dungeons\other\bountylair\bountylair2.json [TMX file differences are left out for huge size.] dungeons\other\bountylair\bountylair3.json [TMX file differences are left out for huge size.] dungeons\other\bountylair\bountylair4.json [TMX file differences are left out for huge size.] dungeons\other\cyberspace\cyberdungeon_middle_1.json [TMX file differences are left out for huge size.] dungeons\other\cyberspace\cyberdungeon_middle_2.json [TMX file differences are left out for huge size.] dungeons\other\cyberspace\start_cyberspace.json [TMX file differences are left out for huge size.] interface\scripted\bountyboard\bountyboardgui.config 710,711c710,711 < "hover" : "/interface/scripted/bountyboard/poster_small_.png", < "pressed" : "/interface/scripted/bountyboard/poster_small_.png", --- > "hover" : "/interface/scripted/bountyboard/poster_small_.png?fade=FFFFFF=0.01", > "pressed" : "/interface/scripted/bountyboard/poster_small_.png?fade=000000=0.01", 759c759 < "position" : [33, 23], --- > "position" : [33, 18], 777,778c777,778 < "hover" : "/interface/scripted/bountyboard/poster_large_.png", < "pressed" : "/interface/scripted/bountyboard/poster_large_.png", --- > "hover" : "/interface/scripted/bountyboard/poster_large_.png?fade=FFFFFF=0.01", > "pressed" : "/interface/scripted/bountyboard/poster_large_.png?fade=000000=0.01", interface\scripted\bountyboard\bountyboardgui.lua 242c242 < local gangLeader = generator:generateBountyNpc(gang, gang.capstoneColor) --- > local gangLeader = generator:generateBountyNpc(gang, gang.capstoneColor, true) 523c523,528 < target = category == "capstone" and self.assignment.gangLeader or generator:generateBountyNpc(gang, colorIndex) --- > if category == "capstone" and self.assignment.gangLeader then > target = self.assignment.gangLeader > else > local withTitle = category == "capstone" or category == "major" > target = generator:generateBountyNpc(gang, colorIndex, withTitle) > end interface\windowconfig\questlog.config 166a167,172 > "imgCurrent" : { > "type" : "image", > "file" : "/interface/quests/questlog/currentworld.png", > "position" : [1, 0], > "zlevel" : 1 > }, 219a226,231 > "zlevel" : 1 > }, > "imgCurrent" : { > "type" : "image", > "file" : "/interface/quests/questlog/currentworld.png", > "position" : [1, 0], monsters\boss\swansong\noxcapture.lua 30a31 > world.sendEntityMessage("teleporterdoor", "openDoor") monsters\boss\swansong\swansong.lua 59c59,61 < --- > > self.gravityDungeonId = config.getParameter("gravityDungeonId") > world.setTileProtection(self.gravityDungeonId, true) 328c330 < world.setDungeonGravity(0, 0) --- > world.setDungeonGravity(self.gravityDungeonId, 0) 341c343 < world.setDungeonGravity(0, 0) --- > world.setDungeonGravity(self.gravityDungeonId, 0) 471c473 < world.setDungeonGravity(0, 50) --- > world.setDungeonGravity(self.gravityDungeonId, 50) 743c745 < world.setDungeonGravity(0, 50) --- > world.setDungeonGravity(self.gravityDungeonId, 50) 768c770 < world.setDungeonGravity(0, 0) --- > world.setDungeonGravity(self.gravityDungeonId, 0) 1307c1309 < world.setDungeonGravity(0, 0) --- > world.setDungeonGravity(self.gravityDungeonId, 0) monsters\boss\swansong\swansong.monstertype 32a33 > "gravityDungeonId" : 666, 44c45 < "dashArea" : 25, // dash if the target is within this range of the center --- > "dashArea" : 20, // dash if the target is within this range of the center 216c217 < "baseValue" : 75.0 --- > "baseValue" : 85.0 monsters\flyers\cosmicintruder\cosmicintruder.monstertype 223c223 < "captureCollectables" : { "raremonsters" : "cosmicintruder" } --- > "captureCollectables" : { "monsters" : "cosmicintruder" } objects\wired\secretdoor\secretdoor.lua 1,117c1,127 < require "/scripts/vec2.lua" < < function init() < self.doorDirection = config.getParameter("doorDirection", "vertical") < self.doorRate = config.getParameter("doorRate", 0) < < if not storage.doorStages then < setupMaterialSpaces() < end < < object.setMaterialSpaces(storage.state and {} or storage.doorStages[#storage.doorStages]) < animator.setAnimationState("doorState", storage.state and "open" or "closed") < < self.doorStageTimer = 0 < < object.setInteractive(not object.isInputNodeConnected(0) and config.getParameter("interactive", true)) < object.setOutputNodeLevel(0, storage.state) < end < < function update(dt) < if self.doorStage then < self.doorStageTimer = self.doorStageTimer - dt < if self.doorStageTimer <= 0 then < advanceDoorStage() < self.doorStageTimer = self.doorRate < end < elseif object.isInputNodeConnected(0) and object.getInputNodeLevel(0) ~= storage.state then < triggerSwitch() < end < end < < function onInteraction(args) < triggerSwitch() < end < < function triggerSwitch() < if not self.doorStage then < storage.state = not storage.state < object.setOutputNodeLevel(0, storage.state) < animator.playSound(storage.state and "open" or "close") < if self.doorRate > 0 then < self.doorStage = storage.state and #storage.doorStages or 0 < self.doorStageTimer = self.doorRate < else < object.setMaterialSpaces(storage.state and {} or storage.doorStages[#storage.doorStages]) < end < end < end < < function advanceDoorStage() < if storage.state then < self.doorStage = self.doorStage - 1 < else < self.doorStage = self.doorStage + 1 < end < < if self.doorStage == 0 or self.doorStage >= #storage.doorStages then < self.doorStage = nil < object.setMaterialSpaces(storage.state and {} or storage.doorStages[#storage.doorStages]) < animator.setAnimationState("doorState", storage.state and "open" or "closed") < else < object.setMaterialSpaces(storage.doorStages[self.doorStage]) < animator.setAnimationState("doorState", "open") < end < end < < function setupMaterialSpaces() < local spaces = object.spaces() < < local pos = entity.position() < local materials = {} < for i, space in ipairs(spaces) do < local mat = world.material(vec2.add(pos, space), "background") < if not mat then < mat = "metamaterial:empty" < end < table.insert(materials, mat) < end < < storage.doorStages = {} < local di = self.doorDirection == "vertical" and 2 or 1 < local min = 1000 < local max = -1000 < for i, space in ipairs(spaces) do < min = math.min(min, space[di]) < max = math.max(max, space[di]) < end < < repeat < local doorStage = {} < for i, space in ipairs(spaces) do < if space[di] <= min or space[di] >= max then < table.insert(doorStage, {space, materials[i]}) < end < end < table.insert(storage.doorStages, doorStage) < < min = min + 1 < max = max - 1 < until min > max < end < < function onNodeConnectionChange() < object.setInteractive(not object.isInputNodeConnected(0) and config.getParameter("interactive", true)) < end < < function openDoor() < if not storage.state and not self.doorStage then < triggerSwitch() < end < end < < function closeDoor() < if storage.state and not self.doorStage then < triggerSwitch() < end < end --- > require "/scripts/vec2.lua" > > function init() > self.doorDirection = config.getParameter("doorDirection", "vertical") > self.doorRate = config.getParameter("doorRate", 0) > > if not storage.doorStages then > setupMaterialSpaces() > end > > object.setMaterialSpaces(storage.state and {} or storage.doorStages[#storage.doorStages]) > animator.setAnimationState("doorState", storage.state and "open" or "closed") > > self.doorStageTimer = 0 > > if storage.locked == nil then > storage.locked = config.getParameter("locked", false) > end > > object.setInteractive(not storage.locked and not object.isInputNodeConnected(0) and config.getParameter("interactive", true)) > object.setOutputNodeLevel(0, storage.state) > > message.setHandler("openDoor", function() openDoor() end) > message.setHandler("closeDoor", function() closeDoor() end) > end > > function update(dt) > if self.doorStage then > self.doorStageTimer = self.doorStageTimer - dt > if self.doorStageTimer <= 0 then > advanceDoorStage() > self.doorStageTimer = self.doorRate > end > elseif object.isInputNodeConnected(0) and object.getInputNodeLevel(0) ~= storage.state then > triggerSwitch() > end > end > > function onInteraction(args) > if not storage.locked then > triggerSwitch() > end > end > > function triggerSwitch() > if not self.doorStage then > storage.state = not storage.state > object.setOutputNodeLevel(0, storage.state) > animator.playSound(storage.state and "open" or "close") > if self.doorRate > 0 then > self.doorStage = storage.state and #storage.doorStages or 0 > self.doorStageTimer = self.doorRate > else > object.setMaterialSpaces(storage.state and {} or storage.doorStages[#storage.doorStages]) > end > end > end > > function advanceDoorStage() > if storage.state then > self.doorStage = self.doorStage - 1 > else > self.doorStage = self.doorStage + 1 > end > > if self.doorStage == 0 or self.doorStage >= #storage.doorStages then > self.doorStage = nil > object.setMaterialSpaces(storage.state and {} or storage.doorStages[#storage.doorStages]) > animator.setAnimationState("doorState", storage.state and "open" or "closed") > else > object.setMaterialSpaces(storage.doorStages[self.doorStage]) > animator.setAnimationState("doorState", "open") > end > end > > function setupMaterialSpaces() > local spaces = object.spaces() > > local pos = entity.position() > local materials = {} > for i, space in ipairs(spaces) do > local mat = world.material(vec2.add(pos, space), "background") > if not mat then > mat = "metamaterial:empty" > end > table.insert(materials, mat) > end > > storage.doorStages = {} > local di = self.doorDirection == "vertical" and 2 or 1 > local min = 1000 > local max = -1000 > for i, space in ipairs(spaces) do > min = math.min(min, space[di]) > max = math.max(max, space[di]) > end > > repeat > local doorStage = {} > for i, space in ipairs(spaces) do > if space[di] <= min or space[di] >= max then > table.insert(doorStage, {space, materials[i]}) > end > end > table.insert(storage.doorStages, doorStage) > > min = min + 1 > max = max - 1 > until min > max > end > > function onNodeConnectionChange() > object.setInteractive(not storage.locked and not object.isInputNodeConnected(0) and config.getParameter("interactive", true)) > end > > function openDoor() > storage.locked = false > if not storage.state and not self.doorStage then > triggerSwitch() > end > end > > function closeDoor() > if storage.state and not self.doorStage then > triggerSwitch() > end > end quests\bounty\bounty.lua 1,430c1,427 < require "/interface/cockpit/cockpitutil.lua" < require "/scripts/messageutil.lua" < require "/scripts/quest/player.lua" < require "/scripts/quest/text_generation.lua" < require "/quests/bounty/bounty_portraits.lua" < require "/quests/bounty/stages.lua" < < function init() < local parameters = quest.parameters() < < storage.pending = storage.pending or {} < storage.spawned = storage.spawned or {} < storage.killed = storage.killed or {} < storage.event = storage.event or {} < storage.scanIds = storage.scanIds or {} < < message.setHandler(quest.questId().."entitySpawned", function(_, _, param, uniqueId) < storage.spawned[param] = uniqueId < storage.pending[param] = nil < end) < message.setHandler(quest.questId().."scanIds", function(_, _, param, uuids) < storage.scanIds[param] = uuids; < end) < message.setHandler(quest.questId().."entityPending", function(_, _, param, position) < storage.pending[param] = position < end) < message.setHandler(quest.questId().."entityDied", function(_, _, param, uniqueId) < storage.killed[param] = uniqueId < end) < message.setHandler(quest.questId()..".participantEvent", function(_, _, uniqueId, eventName, ...) < storage.event[eventName] = true < end) < message.setHandler(quest.questId().."setCompleteMessage", function(_, _, text) < storage.completeMessage = text < end) < message.setHandler(quest.questId().."keepAlive", function() end) < < message.setHandler(quest.questId()..".complete", function(_, _, text) < storage.event["captured"] = true < quest.complete() < end) < < storage.scanObjects = storage.scanObjects or nil < self.scanClue = nil < message.setHandler("objectScanned", function(message, isLocal, objectName) < if storage.scanObjects ~= nil then < storage.scanObjects = copyArray(util.filter(storage.scanObjects, function(n) return n ~= objectName end)) < end < if self.scanClue and objectName == self.scanClue then < storage.event["scannedClue"] = true < end < end) < message.setHandler("interestingObjects", function(...) < return storage.scanObjects or jarray() < end) < < self.stages = util.map(config.getParameter("stages"), function(stageName) < return _ENV[stageName] < end) < < self.radioMessageConfig = { < default = { < messageId = "bounty_message", < unique = false, < senderName = "Captain Noble", < portraitImage = "/interface/chatbubbles/captain.png:" < }, < angry = { < messageId = "bounty_message", < unique = false, < senderName = "Captain Noble", < portraitImage = "/interface/chatbubbles/captainrage.png:" < } < } < < self.defaultSkipMessages = { < "You managed to figure that out without a clue? Nice work!" < } < < self.playingMusic = true < < self.managerPosition = nil < < self.skipMessage = nil < local textParameter = quest.parameters().text < if textParameter then < if not storage.completeMessage then < storage.completeMessage = textParameter.completeMessage < end < self.skipMessage = textParameter.skipMessage or util.randomFromList(self.defaultSkipMessages) < end < < self.bountyType = nil < if #quest.questArcDescriptor().quests > 3 then < self.bountyType = "major" < else < self.bountyType = "minor" < end < < storage.stage = storage.stage or 1 < setStage(storage.stage) < < setText() < < setBountyPortraits() < < self.tasks = {} < < table.insert(self.tasks, coroutine.create(function() < if self.bountyName == nil then < return true < end < while true do < local setBounty = util.await(world.sendEntityMessage(entity.id(), "setBountyName", self.bountyName)) < if setBounty:succeeded() then < break < end < coroutine.yield() < end < return true < end)) < < table.insert(self.tasks, coroutine.create(function() < while storage.scanIds["inertScans"] == nil do < coroutine.yield(false) < end < storage.scanObjects = copyArray(storage.scanIds["inertScans"]) < return true < end)) < < setupEarlyCompletion() < end < < function update(dt) < if not self.managerPosition then < if self.findManager then < local status, result = coroutine.resume(self.findManager) < if not status then < error(result) < end < if result then < self.managerPosition = result < self.findManager = nil < end < elseif questInvolvesWorld() then < sb.logInfo("Find bounty manager") < self.findManager = coroutine.create(loadBountyManager) < 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 < sb.logInfo("Maybe find bounty manager") < self.findManager = coroutine.create(maybeLoadBountyManager) < end < end < < if self.stage then < local status, result = coroutine.resume(self.stage) < if not status then < error(result) < end < end < < self.tasks = util.filter(self.tasks, function(t) < local status, result = coroutine.resume(t) < if not status then < error(result) < end < return not result < end) < end < < function questInvolvesWorld() < local locationsParameter = quest.parameters().locations < if locationsParameter then < local locationWorlds = util.map(util.tableValues(locationsParameter.locations), function(location) < local tags = { < questId = quest.questId() < } < return sb.replaceTags(location.worldId or quest.worldId() or "", tags) < end) < if contains(locationWorlds, player.worldId()) then < return true < end < end < return onQuestWorld() < end < < function onQuestWorld() < return player.worldId() == quest.worldId() and player.serverUuid() == quest.serverUuid() < end < < function stopMusic() < if self.playingMusic then < world.sendEntityMessage(player.id(), "stopAltMusic") < self.playingMusic = false < end < end < < function questStart() < local associatedMission = config.getParameter("associatedMission") < if associatedMission then < player.enableMission(associatedMission) < player.playCinematic(config.getParameter("missionUnlockedCinema")) < end < end < < function questComplete() < stopMusic() < < local quests = quest.questArcDescriptor().quests < -- rewards on last step of the chain < if quest.questId() == quests[#quests].questId then < local rewards = quest.parameters().rewards < local text = config.getParameter("generatedText.complete") < text = text.capture or text.default < < modifyQuestEvents("Captured", rewards.money, rewards.rank, rewards.credits) < < local tags = util.generateTextTags(quest.parameters().text.tags) < tags.bountyPoints = rewards.rank < text = sb.replaceTags(util.randomFromList(text), tags) < quest.setCompletionText(text) < end < < if storage.completeMessage then < player.radioMessage(radioMessage(storage.completeMessage)) < end < < if questInvolvesWorld() then < sb.logInfo("Send playerCompleted message") < world.sendEntityMessage(quest.questArcDescriptor().stagehandUniqueId, "playerCompleted", player.uniqueId(), quest.questId()) < end < < if self.bountyType == "major" then < world.sendEntityMessage(entity.id(), "setBountyName", nil) < end < < local associatedMission = config.getParameter("associatedMission") < if associatedMission then < player.completeMission(associatedMission) < end < < quest.setWorldId(nil) < quest.setLocation(nil) < end < < function questFail(abandoned) < stopMusic() < < modifyQuestEvents("Failed", 0, 0, 0) < < if questInvolvesWorld() then < world.sendEntityMessage(quest.questArcDescriptor().stagehandUniqueId, "playerFailed", player.uniqueId(), quest.questId()) < end < < if self.bountyType == "major" then < world.sendEntityMessage(entity.id(), "setBountyName", nil) < end < -- local failureText = config.getParameter("generatedText.failure") < -- if failureText then < -- quest.setCompletionText(failureText) < -- end < end < < function setupEarlyCompletion() < local questIndices = {} < local quests = quest.questArcDescriptor().quests < for i,q in pairs(quests) do < questIndices[q.questId] = i < end < < for i,q in pairs(quests) do < local spawnsParameter = q.parameters.spawns < if spawnsParameter then < for name,spawnConfig in pairs(spawnsParameter.spawns) do < if spawnConfig.type == "keypad" < and spawnConfig.skipSteps < and spawnConfig.skipSteps > 0 < and i <= questIndices[quest.questId()] < and i + spawnConfig.skipSteps > questIndices[quest.questId()] then < < message.setHandler(q.questId.."keypadUnlocked", function(_, _, _, _) < storage.completeMessage = self.skipMessage < local followup = questIndices[q.questId] + spawnConfig.skipSteps < quest.complete(followup - 1) -- Lua is 1-indexed, callback takes index starting at 0 < end) < end < end < end < end < end < < function questInteract(entityId) < if self.onInteract then < return self.onInteract(entityId) < end < end < < function loadBountyManager() < while true do < local findManager = world.findUniqueEntity(quest.questArcDescriptor().stagehandUniqueId) < while not findManager:finished() do < coroutine.yield() < end < if findManager:succeeded() then < world.sendEntityMessage(quest.questArcDescriptor().stagehandUniqueId, "playerStarted", player.uniqueId(), quest.questId()) < return findManager:result() < else < world.spawnStagehand(entity.position(), "bountymanager", { < uniqueId = quest.questArcDescriptor().stagehandUniqueId, < questArc = quest.questArcDescriptor(), < worldId = player.worldId(), < questId = quest.questId(), < }) < end < coroutine.yield() < end < end < < function maybeLoadBountyManager() < local stagehandId = quest.questArcDescriptor().stagehandUniqueId < while true do < local findManager = util.await(world.findUniqueEntity(stagehandId)) < if findManager:succeeded() then < sb.logInfo("Involves this world: %s", util.await(world.sendEntityMessage(stagehandId, "involvesQuest", quest.questId())):result()) < 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 < < function nextStage() < if storage.stage == #self.stages then < return quest.complete() < end < setStage(storage.stage + 1) < end < < function previousStage() < if storage.state == 1 then < error("Cannot go to previous stage from first stage") < end < setStage(storage.stage - 1) < end < < function setStage(i) < if storage.stage ~= i then < stopMusic() < end < < storage.stage = i < < self.onInteract = nil < self.stage = coroutine.create(self.stages[storage.stage]) < local status, result = coroutine.resume(self.stage) < if not status then < error(result) < end < end < < function setText() < local tags = util.generateTextTags(quest.parameters().text.tags) < self.bountyName = tags["bounty.name"] < local title < if self.bountyType == "major" then < title = sb.replaceTags("^orange;Bounty: ^green;", tags) < else < title = sb.replaceTags("^orange;Minor: ^green;", tags) < end < if onQuestWorld() then < title = title.. " ^yellow;*^reset;" < end < quest.setTitle(title) < < local textCons < for i, q in pairs(quest.questArcDescriptor().quests) do < if i > 1 then -- skip the first quest, it's fake < local questConfig = root.questConfig(q.templateId).scriptConfig < local text = q.parameters.text.questLog < if not text then < if i > 2 then < text = util.randomFromList(questConfig.generatedText.text.prev or questConfig.generatedText.text.default) < else < text = util.randomFromList(questConfig.generatedText.text.default) < end < end < < local tags = util.generateTextTags(q.parameters.text.tags) < if textCons then < textCons = string.format("%s%s", textCons, sb.replaceTags(text, tags)) < else < textCons = sb.replaceTags(text, tags) < end < if q.questId == quest.questId() then < if questConfig.generatedText.failureText then < local failureText = util.randomFromList(questConfig.generatedText.failureText.default) < failureText = sb.replaceTags(failureText, tags) < quest.setFailureText(failureText) < end < < break < end < end < end < < quest.setText(textCons) < end < < function radioMessage(text, portraitType) < portraitType = portraitType or "default" < local message = copy(self.radioMessageConfig[portraitType]) < local tags = util.generateTextTags(quest.parameters().text.tags) < message.text = sb.replaceTags(text, tags) < return message < end < < function modifyQuestEvents(status, money, rank, credits) < local newBountyEvents = player.getProperty("newBountyEvents", {}) < local thisQuestEvents = newBountyEvents[quest.questId()] or {} < thisQuestEvents.status = status < thisQuestEvents.money = (thisQuestEvents.money or 0) + money < thisQuestEvents.rank = (thisQuestEvents.rank or 0) + rank < thisQuestEvents.credits = (thisQuestEvents.credits or 0) + credits < thisQuestEvents.cinematic = config.getParameter("bountyCinematic") < newBountyEvents[quest.questId()] = thisQuestEvents < player.setProperty("newBountyEvents", newBountyEvents) < end --- > require "/interface/cockpit/cockpitutil.lua" > require "/scripts/messageutil.lua" > require "/scripts/quest/player.lua" > require "/scripts/quest/text_generation.lua" > require "/quests/bounty/bounty_portraits.lua" > require "/quests/bounty/stages.lua" > > function init() > local parameters = quest.parameters() > > storage.pending = storage.pending or {} > storage.spawned = storage.spawned or {} > storage.killed = storage.killed or {} > storage.event = storage.event or {} > storage.scanIds = storage.scanIds or {} > > message.setHandler(quest.questId().."entitySpawned", function(_, _, param, uniqueId) > storage.spawned[param] = uniqueId > storage.pending[param] = nil > end) > message.setHandler(quest.questId().."scanIds", function(_, _, param, uuids) > storage.scanIds[param] = uuids; > end) > message.setHandler(quest.questId().."entityPending", function(_, _, param, position) > storage.pending[param] = position > end) > message.setHandler(quest.questId().."entityDied", function(_, _, param, uniqueId) > storage.killed[param] = uniqueId > end) > message.setHandler(quest.questId()..".participantEvent", function(_, _, uniqueId, eventName, ...) > storage.event[eventName] = true > end) > message.setHandler(quest.questId().."setCompleteMessage", function(_, _, text) > storage.completeMessage = text > end) > message.setHandler(quest.questId().."keepAlive", function() end) > > message.setHandler(quest.questId()..".complete", function(_, _, text) > storage.event["captured"] = true > quest.complete() > end) > > storage.scanObjects = storage.scanObjects or nil > self.scanClue = nil > message.setHandler("objectScanned", function(message, isLocal, objectName) > if storage.scanObjects ~= nil then > storage.scanObjects = copyArray(util.filter(storage.scanObjects, function(n) return n ~= objectName end)) > end > if self.scanClue and objectName == self.scanClue then > storage.event["scannedClue"] = true > end > end) > message.setHandler("interestingObjects", function(...) > return storage.scanObjects or jarray() > end) > > self.stages = util.map(config.getParameter("stages"), function(stageName) > return _ENV[stageName] > end) > > self.radioMessageConfig = { > default = { > messageId = "bounty_message", > unique = false, > senderName = "Captain Noble", > portraitImage = "/interface/chatbubbles/captain.png:" > }, > angry = { > messageId = "bounty_message", > unique = false, > senderName = "Captain Noble", > portraitImage = "/interface/chatbubbles/captainrage.png:" > } > } > > self.defaultSkipMessages = { > "You managed to figure that out without a clue? Nice work!" > } > > self.playingMusic = true > > self.managerPosition = nil > > self.skipMessage = nil > local textParameter = quest.parameters().text > if textParameter then > if not storage.completeMessage then > storage.completeMessage = textParameter.completeMessage > end > self.skipMessage = textParameter.skipMessage or util.randomFromList(self.defaultSkipMessages) > end > > self.bountyType = nil > if quest.questArcDescriptor().quests[1].templateId == "pre_bounty" then > self.bountyType = "major" > else > self.bountyType = "minor" > end > > storage.stage = storage.stage or 1 > setStage(storage.stage) > > setText() > > setBountyPortraits() > > self.tasks = {} > > table.insert(self.tasks, coroutine.create(function() > if self.bountyName == nil then > return true > end > while true do > local setBounty = util.await(world.sendEntityMessage(entity.id(), "setBountyName", self.bountyName)) > if setBounty:succeeded() then > break > end > coroutine.yield() > end > return true > end)) > > table.insert(self.tasks, coroutine.create(function() > while storage.scanIds["inertScans"] == nil do > coroutine.yield(false) > end > storage.scanObjects = copyArray(storage.scanIds["inertScans"]) > return true > end)) > > setupEarlyCompletion() > end > > function update(dt) > if not self.managerPosition then > if self.findManager then > local status, result = coroutine.resume(self.findManager) > if not status then > error(result) > end > if result then > self.managerPosition = result > self.findManager = nil > end > elseif questInvolvesWorld() then > sb.logInfo("Find bounty manager") > self.findManager = coroutine.create(loadBountyManager) > 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 > sb.logInfo("Maybe find bounty manager") > self.findManager = coroutine.create(maybeLoadBountyManager) > end > end > > if self.stage then > local status, result = coroutine.resume(self.stage) > if not status then > error(result) > end > end > > self.tasks = util.filter(self.tasks, function(t) > local status, result = coroutine.resume(t) > if not status then > error(result) > end > return not result > end) > end > > function questInvolvesWorld() > local locationsParameter = quest.parameters().locations > if locationsParameter then > local locationWorlds = util.map(util.tableValues(locationsParameter.locations), function(location) > local tags = { > questId = quest.questId() > } > return sb.replaceTags(location.worldId or quest.worldId() or "", tags) > end) > if contains(locationWorlds, player.worldId()) then > return true > end > end > return onQuestWorld() > end > > function onQuestWorld() > return player.worldId() == quest.worldId() and player.serverUuid() == quest.serverUuid() > end > > function stopMusic() > if self.playingMusic then > world.sendEntityMessage(player.id(), "stopAltMusic") > self.playingMusic = false > end > end > > function questStart() > local associatedMission = config.getParameter("associatedMission") > if associatedMission then > player.enableMission(associatedMission) > player.playCinematic(config.getParameter("missionUnlockedCinema")) > end > end > > function questComplete() > stopMusic() > > local quests = quest.questArcDescriptor().quests > -- rewards on last step of the chain > if quest.questId() == quests[#quests].questId then > local rewards = quest.parameters().rewards > local text = config.getParameter("generatedText.complete") > text = text.capture or text.default > > modifyQuestEvents("Captured", rewards.money, rewards.rank, rewards.credits) > > local tags = util.generateTextTags(quest.parameters().text.tags) > tags.bountyPoints = rewards.rank > text = sb.replaceTags(util.randomFromList(text), tags) > quest.setCompletionText(text) > end > > if storage.completeMessage then > player.radioMessage(radioMessage(storage.completeMessage)) > end > > if questInvolvesWorld() then > sb.logInfo("Send playerCompleted message") > world.sendEntityMessage(quest.questArcDescriptor().stagehandUniqueId, "playerCompleted", player.uniqueId(), quest.questId()) > end > > if self.bountyType == "major" then > world.sendEntityMessage(entity.id(), "setBountyName", nil) > end > > local associatedMission = config.getParameter("associatedMission") > if associatedMission then > player.completeMission(associatedMission) > end > > quest.setWorldId(nil) > quest.setLocation(nil) > end > > function questFail(abandoned) > stopMusic() > > modifyQuestEvents("Failed", 0, 0, 0) > > if questInvolvesWorld() then > world.sendEntityMessage(quest.questArcDescriptor().stagehandUniqueId, "playerFailed", player.uniqueId(), quest.questId()) > end > > if self.bountyType == "major" then > world.sendEntityMessage(entity.id(), "setBountyName", nil) > end > -- local failureText = config.getParameter("generatedText.failure") > -- if failureText then > -- quest.setCompletionText(failureText) > -- end > end > > function setupEarlyCompletion() > local questIndices = {} > local quests = quest.questArcDescriptor().quests > for i,q in pairs(quests) do > questIndices[q.questId] = i > end > > for i,q in pairs(quests) do > local spawnsParameter = q.parameters.spawns > if spawnsParameter then > for name,spawnConfig in pairs(spawnsParameter.spawns) do > if spawnConfig.type == "keypad" > and spawnConfig.skipSteps > and spawnConfig.skipSteps > 0 > and i <= questIndices[quest.questId()] > and i + spawnConfig.skipSteps > questIndices[quest.questId()] then > > message.setHandler(q.questId.."keypadUnlocked", function(_, _, _, _) > storage.completeMessage = self.skipMessage > local followup = questIndices[q.questId] + spawnConfig.skipSteps > quest.complete(followup - 1) -- Lua is 1-indexed, callback takes index starting at 0 > end) > end > end > end > end > end > > function questInteract(entityId) > if self.onInteract then > return self.onInteract(entityId) > end > end > > function loadBountyManager() > while true do > local findManager = world.findUniqueEntity(quest.questArcDescriptor().stagehandUniqueId) > while not findManager:finished() do > coroutine.yield() > end > if findManager:succeeded() then > world.sendEntityMessage(quest.questArcDescriptor().stagehandUniqueId, "playerStarted", player.uniqueId(), quest.questId()) > return findManager:result() > else > world.spawnStagehand(entity.position(), "bountymanager", { > uniqueId = quest.questArcDescriptor().stagehandUniqueId, > questArc = quest.questArcDescriptor(), > worldId = player.worldId(), > questId = quest.questId(), > }) > end > coroutine.yield() > end > end > > function maybeLoadBountyManager() > local stagehandId = quest.questArcDescriptor().stagehandUniqueId > while true do > local findManager = util.await(world.findUniqueEntity(stagehandId)) > if findManager:succeeded() then > sb.logInfo("Involves this world: %s", util.await(world.sendEntityMessage(stagehandId, "involvesQuest", quest.questId())):result()) > 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 > > function nextStage() > if storage.stage == #self.stages then > return quest.complete() > end > setStage(storage.stage + 1) > end > > function previousStage() > if storage.state == 1 then > error("Cannot go to previous stage from first stage") > end > setStage(storage.stage - 1) > end > > function setStage(i) > if storage.stage ~= i then > stopMusic() > end > > storage.stage = i > > self.onInteract = nil > self.stage = coroutine.create(self.stages[storage.stage]) > local status, result = coroutine.resume(self.stage) > if not status then > error(result) > end > end > > function setText() > local tags = util.generateTextTags(quest.parameters().text.tags) > self.bountyName = tags["bounty.name"] > local title > if self.bountyType == "major" then > title = sb.replaceTags("^yellow; ^orange;Bounty: ^green;", tags) > else > title = sb.replaceTags("^orange;Bounty: ^green;", tags) > end > quest.setTitle(title) > > local textCons > for i, q in pairs(quest.questArcDescriptor().quests) do > if i > 1 then -- skip the first quest, it's fake > local questConfig = root.questConfig(q.templateId).scriptConfig > local text = q.parameters.text.questLog > if not text then > if i > 2 then > text = util.randomFromList(questConfig.generatedText.text.prev or questConfig.generatedText.text.default) > else > text = util.randomFromList(questConfig.generatedText.text.default) > end > end > > local tags = util.generateTextTags(q.parameters.text.tags) > if textCons then > textCons = string.format("%s%s", textCons, sb.replaceTags(text, tags)) > else > textCons = sb.replaceTags(text, tags) > end > if q.questId == quest.questId() then > if questConfig.generatedText.failureText then > local failureText = util.randomFromList(questConfig.generatedText.failureText.default) > failureText = sb.replaceTags(failureText, tags) > quest.setFailureText(failureText) > end > > break > end > end > end > > quest.setText(textCons) > end > > function radioMessage(text, portraitType) > portraitType = portraitType or "default" > local message = copy(self.radioMessageConfig[portraitType]) > local tags = util.generateTextTags(quest.parameters().text.tags) > message.text = sb.replaceTags(text, tags) > return message > end > > function modifyQuestEvents(status, money, rank, credits) > local newBountyEvents = player.getProperty("newBountyEvents", {}) > local thisQuestEvents = newBountyEvents[quest.questId()] or {} > thisQuestEvents.status = status > thisQuestEvents.money = (thisQuestEvents.money or 0) + money > thisQuestEvents.rank = (thisQuestEvents.rank or 0) + rank > thisQuestEvents.credits = (thisQuestEvents.credits or 0) + credits > thisQuestEvents.cinematic = config.getParameter("bountyCinematic") > newBountyEvents[quest.questId()] = thisQuestEvents > player.setProperty("newBountyEvents", newBountyEvents) > end quests\bounty\bounty_gen.lua 53c53 < local target = generator:generateBountyNpc(gang, nil, behaviorName) --- > local target = generator:generateBountyNpc(gang, nil, false) quests\bounty\dungeons.config 13c13,14 < "fuel_depot" --- > "fueldepot", > "undergroundfueldepot" 27c28,29 < "fuel_depot" --- > "fueldepot", > "undergroundfueldepot" 41c43,44 < "fuel_depot" --- > "fueldepot", > "undergroundfueldepot" 55c58,59 < "fuel_depot" --- > "fueldepot", > "undergroundfueldepot" 69c73,74 < "fuel_depot" --- > "fueldepot", > "undergroundfueldepot" 122c127,131 < "fuel_depot" : { --- > "fueldepot" : { > "type" : "surface", > "tags" : [ "fuel_bounty" ] > }, > "undergroundfueldepot" : { scripts\bountygeneration.lua 182c182 < function BountyGenerator:generateBountyNpc(gang, colorIndex, behaviorName) --- > function BountyGenerator:generateBountyNpc(gang, colorIndex, withTitle) 198,203c198,205 < if self.rand:randf() < 0.5 then < -- name prefix < name = string.format("%s%s", util.randomFromList(bountyConfig.prefix, self.rand), name) < else < -- name suffix < name = string.format("%s%s", name, util.randomFromList(bountyConfig.suffix, self.rand)) --- > if withTitle then > if self.rand:randf() < 0.5 then > -- name prefix > name = string.format("%s%s", util.randomFromList(bountyConfig.prefix, self.rand), name) > else > -- name suffix > name = string.format("%s%s", name, util.randomFromList(bountyConfig.suffix, self.rand)) > end 207c209 < local behaviorModifier = bountyConfig.behaviorModifiers[behaviorName or util.randomFromList(modifierNames, self.rand)] --- > local behaviorModifier = bountyConfig.behaviorModifiers[util.randomFromList(modifierNames, self.rand)] stats\effects\maxprotection\loweredprotection.statuseffect 4c4 < "protection" : -75.0, --- > "protection" : -85.0, tilesets\packed\miscellaneous.json 8c8 < "tilecount":23, --- > "tilecount":24, 116a117,123 > "23": > { > "\/\/description":"Require that there be ocean liquid", > "\/\/shortdescription":"World Gen Must Contain Ocean Liquid", > "allowOverdrawing":"true", > "worldGenMustNotContainLiquid":"" > }, 228a236,239 > }, > "23": > { > "image":"..\/..\/..\/..\/tiled\/packed\/miscellaneous\/23.png" tilesets\packed\objects-by-category\actionfigure.json 7c7 < "tilecount" : 67, --- > "tilecount" : 69, 585a586,603 > "67" : { > "//description" : "The label reads, \"An entity born outside of physical space and time.\"", > "//name" : "cosmicintruderaf", > "//shortdescription" : "Cosmic Intruder Figurine", > "imagePositionX" : "-8", > "imagePositionY" : "0", > "object" : "cosmicintruderaf", > "tilesetDirection" : "right" > }, > "68" : { > "//description" : "The label reads, \"An experimental mech suit, built and piloted by Asra Nox.\"", > "//name" : "swansongaf", > "//shortdescription" : "Swansong Figurine", > "imagePositionX" : "-8", > "imagePositionY" : "0", > "object" : "swansongaf", > "tilesetDirection" : "right" > }, 805a824,829 > }, > "67" : { > "image" : "../../../../../tiled/packed/objects/cosmicintruderaf.png" > }, > "68" : { > "image" : "../../../../../tiled/packed/objects/swansongaf.png" tilesets\packed\objects-by-category\decorative.json 7c7 < "tilecount" : 1436, --- > "tilecount" : 1437, 4017c4017 < "//description" : "A worn wall banner, spraypainted with the Ocassus cult emblem.", --- > "//description" : "A worn wall banner, spraypainted with the Occasus cult emblem.", 4034c4034 < "//description" : "A worn wall banner, spraypainted with the Ocassus cult emblem.", --- > "//description" : "A worn wall banner, spraypainted with the Occasus cult emblem.", 4043c4043 < "//description" : "A worn wall banner, spraypainted with the Ocassus cult emblem.", --- > "//description" : "A worn wall banner, spraypainted with the Occasus cult emblem.", 4210a4211,4218 > "1436" : { > "//description" : "A clue for a bounty.", > "//name" : "scanclue2", > "//shortdescription" : "Scan Clue 2", > "imagePositionX" : "-8", > "imagePositionY" : "-8", > "object" : "scanclue2" > }, 13479a13488,13490 > }, > "1436" : { > "image" : "../../../../../tiled/packed/objects/scanclue2.png" tilesets\packed\objects-by-category\furniture.json 2461c2461,2462 < "object" : "egyptianpillar" --- > "object" : "egyptianpillar", > "tilesetDirection" : "right" 2478c2479,2480 < "object" : "egyptianstatuette" --- > "object" : "egyptianstatuette", > "tilesetDirection" : "right" tilesets\packed\objects-by-category\generic.json 20d19 < "//description" : "Extracts ores from the ground", 22,25c21 < "//shortdescription" : "Extractor Drill", < "imagePositionX" : "-16", < "imagePositionY" : "0", < "object" : "extractordrill" --- > "invalid" : "true" 33c29 < "image" : "../../../../../tiled/packed/objects/extractordrill.png" --- > "image" : "../../../../../tiled/packed/../packed/invalid.png" tilesets\packed\objects-by-category\light.json 7c7 < "tilecount" : 457, --- > "tilecount" : 458, 3501a3502,3510 > "457" : { > "//description" : "A large ornate oil lamp that burns with a soft blue flame.", > "//name" : "egyptianlamp", > "//shortdescription" : "Egyptian Lamp", > "imagePositionX" : "-16", > "imagePositionY" : "0", > "object" : "egyptianlamp", > "tilesetDirection" : "right" > }, 5209a5219,5221 > }, > "457" : { > "image" : "../../../../../tiled/packed/objects/egyptianlamp.png" tilesets\packed\objects-by-category\teleporter.json 7c7 < "tilecount" : 41, --- > "tilecount" : 42, 308a309,316 > "41" : { > "//description" : "This teleporter will take you back to where you came from.", > "//name" : "returnteleporter", > "//shortdescription" : "Return Teleporter", > "imagePositionX" : "-16", > "imagePositionY" : "0", > "object" : "returnteleporter" > }, 458a467,469 > }, > "41" : { > "image" : "../../../../../tiled/packed/objects/returnteleporter.png" tilesets\packed\objects-by-colonytag\commerce.json 522c522,523 < "object" : "egyptianpillar" --- > "object" : "egyptianpillar", > "tilesetDirection" : "right" tilesets\packed\objects-by-colonytag\cultist.json 11c11 < "//description" : "A worn wall banner, spraypainted with the Ocassus cult emblem.", --- > "//description" : "A worn wall banner, spraypainted with the Occasus cult emblem.", 38c38 < "//description" : "A worn wall banner, spraypainted with the Ocassus cult emblem.", --- > "//description" : "A worn wall banner, spraypainted with the Occasus cult emblem.", 47c47 < "//description" : "A worn wall banner, spraypainted with the Ocassus cult emblem.", --- > "//description" : "A worn wall banner, spraypainted with the Occasus cult emblem.", tilesets\packed\objects-by-colonytag\egyptian.json 7c7 < "tilecount" : 6, --- > "tilecount" : 7, 43c43,44 < "object" : "egyptianpillar" --- > "object" : "egyptianpillar", > "tilesetDirection" : "right" 60c61,71 < "object" : "egyptianstatuette" --- > "object" : "egyptianstatuette", > "tilesetDirection" : "right" > }, > "6" : { > "//description" : "A large ornate oil lamp that burns with a soft blue flame.", > "//name" : "egyptianlamp", > "//shortdescription" : "Egyptian Lamp", > "imagePositionX" : "-16", > "imagePositionY" : "0", > "object" : "egyptianlamp", > "tilesetDirection" : "right" 80a92,94 > }, > "6" : { > "image" : "../../../../../tiled/packed/objects/egyptianlamp.png" tilesets\packed\objects-by-colonytag\light.json 7c7 < "tilecount" : 368, --- > "tilecount" : 369, 2621a2622,2630 > "368" : { > "//description" : "A large ornate oil lamp that burns with a soft blue flame.", > "//name" : "egyptianlamp", > "//shortdescription" : "Egyptian Lamp", > "imagePositionX" : "-16", > "imagePositionY" : "0", > "object" : "egyptianlamp", > "tilesetDirection" : "right" > }, 4123a4133,4135 > }, > "368" : { > "image" : "../../../../../tiled/packed/objects/egyptianlamp.png" tilesets\packed\objects-by-colonytag\misc.json 7c7 < "tilecount" : 166, --- > "tilecount" : 168, 651a652,667 > "166" : { > "//description" : "This teleporter will take you back to where you came from.", > "//name" : "returnteleporter", > "//shortdescription" : "Return Teleporter", > "imagePositionX" : "-16", > "imagePositionY" : "0", > "object" : "returnteleporter" > }, > "167" : { > "//description" : "A clue for a bounty.", > "//name" : "scanclue2", > "//shortdescription" : "Scan Clue 2", > "imagePositionX" : "-8", > "imagePositionY" : "-8", > "object" : "scanclue2" > }, 1624a1641,1646 > }, > "166" : { > "image" : "../../../../../tiled/packed/objects/returnteleporter.png" > }, > "167" : { > "image" : "../../../../../tiled/packed/objects/scanclue2.png" tilesets\packed\objects-by-colonytag\pretty.json 7c7 < "tilecount" : 477, --- > "tilecount" : 479, 3662c3662,3663 < "object" : "egyptianstatuette" --- > "object" : "egyptianstatuette", > "tilesetDirection" : "right" 3671a3673,3690 > "477" : { > "//description" : "The label reads, \"An entity born outside of physical space and time.\"", > "//name" : "cosmicintruderaf", > "//shortdescription" : "Cosmic Intruder Figurine", > "imagePositionX" : "-8", > "imagePositionY" : "0", > "object" : "cosmicintruderaf", > "tilesetDirection" : "right" > }, > "478" : { > "//description" : "The label reads, \"An experimental mech suit, built and piloted by Asra Nox.\"", > "//name" : "swansongaf", > "//shortdescription" : "Swansong Figurine", > "imagePositionX" : "-8", > "imagePositionY" : "0", > "object" : "swansongaf", > "tilesetDirection" : "right" > }, 5432a5452,5457 > }, > "477" : { > "image" : "../../../../../tiled/packed/objects/cosmicintruderaf.png" > }, > "478" : { > "image" : "../../../../../tiled/packed/objects/swansongaf.png" tilesets\packed\objects-by-race\generic.json 7c7 < "tilecount" : 1973, --- > "tilecount" : 1978, 8913d8912 < "//description" : "Extracts ores from the ground", 8915,8918c8914 < "//shortdescription" : "Extractor Drill", < "imagePositionX" : "-16", < "imagePositionY" : "0", < "object" : "extractordrill" --- > "invalid" : "true" 8926c8922,8923 < "object" : "egyptianpillar" --- > "object" : "egyptianpillar", > "tilesetDirection" : "right" 8961c8958,8959 < "object" : "egyptianstatuette" --- > "object" : "egyptianstatuette", > "tilesetDirection" : "right" 9173a9172,9214 > "1973" : { > "//description" : "A large ornate oil lamp that burns with a soft blue flame.", > "//name" : "egyptianlamp", > "//shortdescription" : "Egyptian Lamp", > "imagePositionX" : "-16", > "imagePositionY" : "0", > "object" : "egyptianlamp", > "tilesetDirection" : "right" > }, > "1974" : { > "//description" : "The label reads, \"An entity born outside of physical space and time.\"", > "//name" : "cosmicintruderaf", > "//shortdescription" : "Cosmic Intruder Figurine", > "imagePositionX" : "-8", > "imagePositionY" : "0", > "object" : "cosmicintruderaf", > "tilesetDirection" : "right" > }, > "1975" : { > "//description" : "The label reads, \"An experimental mech suit, built and piloted by Asra Nox.\"", > "//name" : "swansongaf", > "//shortdescription" : "Swansong Figurine", > "imagePositionX" : "-8", > "imagePositionY" : "0", > "object" : "swansongaf", > "tilesetDirection" : "right" > }, > "1976" : { > "//description" : "This teleporter will take you back to where you came from.", > "//name" : "returnteleporter", > "//shortdescription" : "Return Teleporter", > "imagePositionX" : "-16", > "imagePositionY" : "0", > "object" : "returnteleporter" > }, > "1977" : { > "//description" : "A clue for a bounty.", > "//name" : "scanclue2", > "//shortdescription" : "Scan Clue 2", > "imagePositionX" : "-8", > "imagePositionY" : "-8", > "object" : "scanclue2" > }, 19804c19845 < "image" : "../../../../../tiled/packed/objects/extractordrill.png" --- > "image" : "../../../../../tiled/packed/../packed/invalid.png" 19891a19933,19947 > }, > "1973" : { > "image" : "../../../../../tiled/packed/objects/egyptianlamp.png" > }, > "1974" : { > "image" : "../../../../../tiled/packed/objects/cosmicintruderaf.png" > }, > "1975" : { > "image" : "../../../../../tiled/packed/objects/swansongaf.png" > }, > "1976" : { > "image" : "../../../../../tiled/packed/objects/returnteleporter.png" > }, > "1977" : { > "image" : "../../../../../tiled/packed/objects/scanclue2.png" tilesets\packed\objects-by-race\human.json 1740c1740 < "//description" : "A worn wall banner, spraypainted with the Ocassus cult emblem.", --- > "//description" : "A worn wall banner, spraypainted with the Occasus cult emblem.", 1749c1749 < "//description" : "A worn wall banner, spraypainted with the Ocassus cult emblem.", --- > "//description" : "A worn wall banner, spraypainted with the Occasus cult emblem.", 1758c1758 < "//description" : "A worn wall banner, spraypainted with the Ocassus cult emblem.", --- > "//description" : "A worn wall banner, spraypainted with the Occasus cult emblem.",