diff --git a/objects/Bonded.c5261f.json b/objects/Bonded.c5261f.json index 9c4ac66c..68d6d6da 100644 --- a/objects/Bonded.c5261f.json +++ b/objects/Bonded.c5261f.json @@ -5,31 +5,11 @@ "z": 0 }, "Autoraise": true, - "Bag": { - "Order": 0 - }, "ColorDiffuse": { "b": 1, "g": 1, "r": 1 }, - "ContainedObjects_order": [ - "GuardianoftheCrystallizer.100e24", - "Augur.143e38", - "Bloodlust.14c7e0", - "WishEater.20570c", - "PendantoftheQueen.29f699", - "TheStarsAreRight.406a2b", - "UnboundBeast.4a8eef", - "Blood-Rite.4cc413", - "Hope.4da36d", - "Zeal.506382", - "EssenceoftheDream.6ad46b", - "SoothingMelody.6ced1f", - "DreamParasite.cb91bd", - "Dream-Gate.fa4c1e" - ], - "ContainedObjects_path": "Bonded.c5261f", "CustomMesh": { "CastShadows": true, "ColliderURL": "", @@ -48,7 +28,7 @@ "MaterialIndex": 3, "MeshURL": "https://pastebin.com/raw/ALrYhQGb", "NormalURL": "", - "TypeIndex": 6 + "TypeIndex": 0 }, "Description": "", "DragSelectable": true, @@ -61,12 +41,10 @@ "IgnoreFoW": false, "LayoutGroupSortIndex": 0, "Locked": true, - "LuaScriptState_path": "Bonded.c5261f.luascriptstate", - "LuaScript_path": "Bonded.c5261f.ttslua", - "MaterialIndex": -1, + "LuaScript": "require(\"playercards/spawnbag/BondedBag\")", + "LuaScriptState": "{\"spawnBagState\":{\"placed\":[],\"placedObjects\":[]}}", "MeasureMovement": false, - "MeshIndex": -1, - "Name": "Custom_Model_Bag", + "Name": "Custom_Model", "Nickname": "Bonded", "Snap": true, "Sticky": true, diff --git a/src/playercards/PlayerCardSpawner.ttslua b/src/playercards/PlayerCardSpawner.ttslua index d0e1eef6..3b2d6034 100644 --- a/src/playercards/PlayerCardSpawner.ttslua +++ b/src/playercards/PlayerCardSpawner.ttslua @@ -1,3 +1,9 @@ + +-- Amount to shift for the next card (zShift) or next row of cards (xShift) +-- Note that the table rotation is weird, and the X axis is vertical while the +-- Z axis is horizontal +local zShift = -2.29998 + Spawner = { } -- Spawns a list of cards at the given position/rotation. This will separate cards by size - @@ -39,6 +45,18 @@ Spawner.spawnCards = function(cardList, pos, rot, sort, callback) Spawner.spawn(miniCards, position, rot, callback) end +Spawner.spawnCardSpread = function(cardList, startPos, rot, sort, callback) + if (sort) then + table.sort(cardList, Spawner.cardComparator) + end + + local position = { x = startPos.x, y = startPos.y, z = startPos.z } + for _, card in ipairs(cardList) do + Spawner.spawn({ card }, position, rot, callback) + position.z = position.z + zShift + end +end + -- Spawn a specific list of cards. This method is for internal use and should not be called -- directly, use spawnCards instead. -- @param cardList: A list of Player Card data structures (data/metadata) diff --git a/src/playercards/spawnbag/BondedBag.ttslua b/src/playercards/spawnbag/BondedBag.ttslua new file mode 100644 index 00000000..b2a5bec3 --- /dev/null +++ b/src/playercards/spawnbag/BondedBag.ttslua @@ -0,0 +1,63 @@ +require("playercards/spawnbag/SpawnBag") + +SPAWN_SPEC = { + name = "BondedCards", + cards = { + "05314", -- Soothing Melody + "06277", -- Wish Eater + "06019", -- Bloodlust + "06022", -- Pendant of the Queen + "05317", -- Blood-rite + "06113", -- Essence of the Dream + "06028", -- Stars Are Right + "06025", -- Guardian of the Crystallizer + "06283", -- Unbound Beast + "06032", -- Zeal + "06031", -- Hope + "06033", -- Augur + "06331", -- Dream Parasite + "06015a", -- Dream-Gate + }, + globalPos = { x = -33.88, y = 1.5, z = 85.61 }, + rotation = { x = 0, y = 270, z = 0 }, + spread = true, +} + +function onLoad(savedData) + if (savedData ~= nil) then + local saveState = JSON.decode(savedData) + if (saveState.spawnBagState ~= nil) then + SpawnBag.loadFromSave(saveState.spawnBagState) + end + end + createActionButtons() +end + +function onSave() + local saveState = { + spawnBagState = SpawnBag.getStateForSave(), + } + return JSON.encode(saveState) +end + +function createActionButtons() + self.createButton({ + label="Place", click_function="buttonClick_place", function_owner=self, + position={1,0.1,2.1}, rotation={0,0,0}, height=350, width=800, + font_size=250, color={0,0,0}, font_color={1,1,1} + }) + self.createButton({ + label="Recall", click_function="buttonClick_recall", function_owner=self, + position={-1,0.1,2.1}, rotation={0,0,0}, height=350, width=800, + font_size=250, color={0,0,0}, font_color={1,1,1} + }) +end + +function buttonClick_place() + SpawnBag.spawn(SPAWN_SPEC) +end + +--Recalls objects to bag from table +function buttonClick_recall() + SpawnBag.recall() +end diff --git a/src/playercards/spawnbag/SpawnBag.ttslua b/src/playercards/spawnbag/SpawnBag.ttslua new file mode 100644 index 00000000..dfd70325 --- /dev/null +++ b/src/playercards/spawnbag/SpawnBag.ttslua @@ -0,0 +1,202 @@ +require("playercards/PlayerCardSpawner") + +-- Allows spawning of defined lists of cards which will be created from the template in the All +-- Player Cards bag. SpawnBag.spawn will create objects based on a table definition, while +-- SpawnBag.recall will clean them all up. Recall will be limited to a small area around the +-- spawned objects. Objects moved out of this area will not be cleaned up. +-- +-- SpawnSpec: Spawning requires a spawn specification with the following structure: +-- { +-- name: Name of this spawn content, used for internal tracking. Multiple specs can be spawned, +-- but each requires a separate name +-- cards: A list of card IDs to be spawned +-- globalPos: Where the spawned objects should be placed, in global coordinates. This should be +-- a valid Vector with x, y, and z defined, e.g. { x = 5, y = 1, z = 15 } +-- rotation: Rotation for the spawned objects. X=180 should be used for face down items. As with +-- globalPos, this should be a valid Vector with x, y, and z defined +-- spread: Optional. If present and true, cards will be spawned next to each other in a spread +-- moving to the right. globalPos will define the location of the first card, each after that +-- will be moved a predefined distance +-- } +-- See BondedBag.ttslua for an example + +SpawnBag = { } + +-- To assist debugging, will draw a box around the recall zone when it's set up +local SHOW_RECALL_ZONE = false + +local ALL_CARDS_GUID = "15bb07" + +-- Distance to expand the recall zone around any added object. +local RECALL_BUFFER_X = 0.9 +local RECALL_BUFFER_Z = 0.5 + +-- In order to mimic the behavior of the previous memory buttons we use a temporary bag when +-- recalling objects. This bag is tiny and transparent, and will be placed at the same location as +-- this object. Once all placed cards are recalled bag to this bag, it will be destroyed +local RECALL_BAG = { + Name = "Bag", + Transform = { + scaleX = 0.01, + scaleY = 0.01, + scaleZ = 0.01, + }, + ColorDiffuse = { + r = 0, + g = 0, + b = 0, + a = 0, + }, + Locked = true, + Grid = true, + Snap = false, + Tooltip = false, +} + + +-- Tracks what has been placed by this "bag" so they can be recalled +local placedSpecs = { } +local placedObjectGuids = { } +local recallZone = nil + +-- Loads a table of saved state, extracted during the parent object's onLoad +SpawnBag.loadFromSave = function(saveTable) + placedSpecs = saveTable.placed + placedObjectGuids = saveTable.placedObjects + recallZone = saveTable.recall +end + +-- Generates a table of save state that can be included in the parent object's onSave +SpawnBag.getStateForSave = function() + local saveState = { + placed = placedSpecs, + placedObjects = placedObjectGuids, + recall = recallZone, + } + + return saveState +end + +-- Places the given spawnSpec on the table. See SpawnBag.ttslua header for spawnSpec table data and +-- examples +SpawnBag.spawn = function(spawnSpec) + -- Limit to one placement at a time + if (placedSpecs[spawnSpec.name]) then + return + end + if (spawnSpec == nil) then + -- TODO: error here + return + end + local cardsToSpawn = { } + local allCardsBag = getObjectFromGUID(ALL_CARDS_GUID) + for _, cardId in ipairs(spawnSpec.cards) do + local cardData = allCardsBag.call("getCardById", { id = cardId }) + if (cardData ~= nil) then + table.insert(cardsToSpawn, cardData) + else + -- TODO: error here + end + end + if (spawnSpec.spread) then + Spawner.spawnCardSpread(cardsToSpawn, spawnSpec.globalPos, spawnSpec.rotation, false, recordPlacedObject) + else + Spawner.spawnCards(cardsToSpawn, spawnSpec.globalPos, spawnSpec.rotation, false, recordPlacedObject) + end + placedSpecs[spawnSpec.name] = true +end + +-- Recalls all spawned objects to the bag, and clears the placedObjectGuids list +SpawnBag.recall = function() + local trash = spawnObjectData({data = RECALL_BAG, position = self.getPosition()}) + for guid, _ in pairs(placedObjectGuids) do + local obj = getObjectFromGUID(guid) + if (obj ~= nil) then + if (isInRecallZone(obj)) then + trash.putObject(obj) + end + placedObjectGuids[guid] = nil + end + end + + trash.destruct() + -- We've recalled everything we can, some cards may have been moved out of the + -- card area. Just reset at this point. + placedSpecs = { } + placedObjectGuids = { } + recallZone = nil +end + +-- Callback for when an object has been spawned. Tracks the object for later recall and updates the +-- recall zone. +function recordPlacedObject(spawned) + placedObjectGuids[spawned.getGUID()] = true + expandRecallZone(spawned) +end + +-- Expands the current recall zone based on the position of the given object. The recall zone will +-- be maintained as the bounding box of the extreme object positions, plus a small amount of buffer +function expandRecallZone(spawnedCard) + local pos = spawnedCard.getPosition() + if (recallZone == nil) then + -- First card out of the bag, initialize surrounding that + recallZone = { } + recallZone.upperLeft = { x = pos.x + RECALL_BUFFER_X, z = pos.z + RECALL_BUFFER_Z } + recallZone.lowerRight = { x = pos.x - RECALL_BUFFER_X, z = pos.z - RECALL_BUFFER_Z } + return + else + if (pos.x > recallZone.upperLeft.x) then + recallZone.upperLeft.x = pos.x + RECALL_BUFFER_X + end + if (pos.x < recallZone.lowerRight.x) then + recallZone.upperLeft.x = pos.x - RECALL_BUFFER_X + end + if (pos.z > recallZone.upperLeft.z) then + recallZone.upperLeft.z = pos.z + RECALL_BUFFER_Z + end + if (pos.z < recallZone.lowerRight.z) then + recallZone.lowerRight.z = pos.z - RECALL_BUFFER_Z + end + end + if (SHOW_RECALL_ZONE) then + local y = 1.5 + local thick = 0.05 + Global.setVectorLines({ + { + points = { {recallZone.upperLeft.x,y,recallZone.upperLeft.z}, {recallZone.upperLeft.x,y,recallZone.lowerRight.z} }, + color = {1,0,0}, + thickness = thick, + rotation = {0,0,0}, + }, + { + points = { {recallZone.upperLeft.x,y,recallZone.lowerRight.z}, {recallZone.lowerRight.x,y,recallZone.lowerRight.z} }, + color = {1,0,0}, + thickness = thick, + rotation = {0,0,0}, + }, + { + points = { {recallZone.lowerRight.x,y,recallZone.lowerRight.z}, {recallZone.lowerRight.x,y,recallZone.upperLeft.z} }, + color = {1,0,0}, + thickness = thick, + rotation = {0,0,0}, + }, + { + points = { {recallZone.lowerRight.x,y,recallZone.upperLeft.z}, {recallZone.upperLeft.x,y,recallZone.upperLeft.z} }, + color = {1,0,0}, + thickness = thick, + rotation = {0,0,0}, + }, + }) + end +end + +-- Checks to see if the given object is in the current recall zone. If there isn't a recall zone, +-- will return true so that everything can be easily cleaned up. +function isInRecallZone(obj) + if (recallZone == nil) then + return true + end + local pos = obj.getPosition() + return (pos.x < recallZone.upperLeft.x and pos.x > recallZone.lowerRight.x + and pos.z < recallZone.upperLeft.z and pos.z > recallZone.lowerRight.z) +end