Friday, April 22, 2022

Coding in Tabletop Simulator (TTS)

 Sometimes, I feel like my code is really elegant. I have the bits I need and there isn't a ton of logic to flesh out, so something that felt complicated when I was thinking about adding it turns out to be pretty simple when I actually write the bits:


--Build loop index

  holdIndex = {sactionHold, kactionHold}

  actionIndex = {sactionZone, kactionZone}

  startingPosition = {8.5, -9.2}

  incrementValues = {1, -1}

  --loop through both sides to clear action dice

  for i = 1, 2 do

    startPlace = startingPosition[i]

    advanceVal = incrementValues[i]

    actionHold = getObjectFromGUID(holdIndex[i])

    actionObjects = getObjectFromGUID(actionIndex[i]).getObjects()

    --rebuild holding zone

    Global.call("rebuildHold", {holdIndex[i], startPlace, advanceVal, actionIndex[i]})

    for _, die in ipairs(actionObjects) do

      if die.hasTag("action") then

        if die.getName() == "Footman" or die.getName() == "Breacher" or die.getName() == "Berserker" then

          --just remove the action die from the game by destroying it.

          --Battalion cannot change lanes, so when a lane becomes inactive, the Battalion's actions become meaningless until next round

          die.destruct()

        else

          dPos = {startPlace, 1.16, 27}

          die.setPosition(dPos)

          die.setLock(true)

          startPlace = startPlace + advanceVal

        end

      end

    end

  end


This bit uses two scripting zones I placed on the table (four actually, because everything is divided into the two sides). As I've mentioned before, I tend to just focus on getting parts working more than on writing clean or simple or efficient code. In fact, in the past, I've mostly only cleaned up inefficient code when it was actually affecting performance (like calling 10 queries on a database in order to keep the data set small instead of calling 1 query and using a very large data set to resolve everything. I spent a year cleaning this type of methodology up at work)

The first, obvious thing you'll notice if you're opening up my code is that most of the foundational bits I've written are divided into two parts - one for one side of the table and the second for the other side. It still works this way, of course, but now I am (duh) using a loop and a couple indexes to divide up the unique identities for each side. 

actionHold is the part on the side of the table where the action dice are initially set up for the round. 

actionZone is the actual tile between the lane Guardians where players can interact with their action dice. 

All this routine is doing is taking all of the action dice off of the action tile and placing them back into the holding area. This is to support the concept that a Champion which still possesses an action may be moved to an active lane and still use their action. So, I figured the logic for this was to 1) establish a die at a set point in the game which either exists or doesn't exist, 2) move that die into the action zone any time the Champion is moved into an active lane, 3) preserve the die whenever a lane goes inactive without the Champion using their action, 4) remove all remaining dice at the end of the round prior to setting up the next round. That's why I figured a holding zone and an active zone would be ideal.

As far as the holding zone being visible to the players, I am not sure what direction would be best on that. I definitely considered keeping the holding zone hidden, so that the only time the players see the action dice is when they are placed on the action tile. This could streamline player recognition of what the dice are and what to use them for. On the other hand, there is benefit to having them visible to both players throughout the round. It gives an overall view of what the round will look like. 

We talked about the die removal (which is not the above function). I am still torn about just assuming that any battalion being removed from the board is going to take an action with them. This is actually based more on the times when a player might choose which of their own Battalion to remove than on when their opponent will defeat one. There are circumstances, like using a Paragon reaction or just dealing with untargeted damage, that a player will decide which battalion to remove. For these circumstances, it is best to let the player manage the removal of the dice. 

This, of course, means that there is potential confusion. If a player does not remove a die when they are supposed to do so, then they might mistakenly (or purposely) gain extra actions that they should not have just because the dice exist for the sake of clarity (clarity causing obfuscation). This is really a result of the fact that is is INCREDIBLY inconvenient to tie action dice to any specific card in the game. There is an expansion concept which would give all battalion individual names and portraits. In this version, sure, it would be easy to tie the dice to the cards. But, right now, not so much. It's definitely something that could be handled very cleanly outside of TTS.

Anyway, the basics here are:

Find where the first die should be placed  when moved to the holding zone: startingPosition

Find out how many units to separate the dice when subsequent dice are moved: incrementValues

Call a different routine which counts how many dice are going to be moved and moves everything already in the holding zone enough distance away from the starting position to make room for the dice being moved in: rebuildHold

Loop through all dice in the action zone and destroy any battalion dice while moving any Champion dice to the holding zone.

When a die is moved, advance the startingPosition value so the next die which gets moved will not get moved to the exact same position (not really a problem other than the visual confusion it causes and the fact that it sort of defeats the whole purpose of having the dice in the holding zone visible to the players in the first place.) If I were hiding the dice being held, then this whole thing would be a bit simpler.

No comments:

Post a Comment