Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix cards in more situations #96

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
144 changes: 97 additions & 47 deletions src/Global.-1.ttslua
Original file line number Diff line number Diff line change
Expand Up @@ -659,15 +659,6 @@ function getClueDetails(processedClue)
end
end

function rotateclues()
for cardIndex, cardData in ipairs(cards) do
local card = getObjectFromGUID(cardData.guid)
if card.interactable then
card.setRotation({0, 180, 0})
end
end
end

function encodeClue(color, clue)
local finishedClue
local cluePosition
Expand Down Expand Up @@ -730,11 +721,34 @@ function encodeClue(color, clue)
end
end

function onObjectEnterContainer(deck, card)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function intercepts all objects that enter any container and deletes the container, so I made it more discriminatory.

-- First to check that we only do this on the first grouping
if #deck.getObjects() == 2 then
deck.destruct()
Wait.frames(function() dealCards() end, 180)
function isCodenamesCard(obj)
if obj.type == "Card" then
vals = obj.getCustomObject()
if vals.face != nil and string.find(vals.face,"codenames")then
Copy link
Contributor Author

@Canonelis Canonelis Dec 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I made a function that should work for future implementations. If the object is of type "Card" and the face image URL contains "codenames" then it is probably a codenames card. Agents don't have a type of "Card" and currently no other game elements have a card type in your implementation.
The getCustomObject() function never seems to return "nil" and returns an empty array if there are no elements.

return true
end
end
return false
end

function isCodenamesClue(obj)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a very discriminatory function to identify clue tiles.

if obj.type == "Board" then
butts = obj.getButtons()
if butts != nil and (#butts == 1 and butts[1].click_function == "nullFunction" or #butts == 2 and butts[1].click_function == "clue") and butts[1].scale == Vector(2, 2, 1.3333) and butts[1].position == Vector(0,0.2,0) then
return true
end
end
return false
end

function onObjectEnterContainer(container, object)
-- if the displaced object is a codenames card then delete the container and refresh the cards
if isCodenamesCard(object) then
-- Check whether container is being destroyed so we only do this once for multiple deposited objects
if not container.isDestroyed() then
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The .isDestroyed() is perfect for this situation. It returns true if .destruct() was called on it even if the object hasn't had any frames to be removed from the game yet.

container.destruct()
dealCardsDelayed()
end
end
end

Expand Down Expand Up @@ -924,12 +938,9 @@ function resetGame()
end

-- Delete clue tiles
for _, clueList in ipairs({gameState.redClues, gameState.blueClues}) do
for _, clue in ipairs(clueList) do
local clueTile = getObjectFromGUID(clue)
if clueTile then
clueTile.destruct()
end
for _, obj in pairs(getObjects()) do
Copy link
Contributor Author

@Canonelis Canonelis Dec 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codenames keeps track of clue tile guid's but this doesn't get saved or loaded when a rewind or host change happens. So let's just delete all clue tiles when a new game starts. In my experience with puzzles with 5000 pieces, this is still very fast to run.

if isCodenamesClue(obj) then
obj.destruct()
end
end

Expand Down Expand Up @@ -976,6 +987,7 @@ function setupGame()

-- Set the correct double card to red
extraCard.setLock(false)
extraCard.setRotation({0, 180, 180})
extraCard.setPositionSmooth(agents[extraCard.guid].position)
extraCard.interactable = true
agents[extraCard.guid].enabled = true
Expand All @@ -997,8 +1009,23 @@ function setupGame()
gameState.firstTurn = true
end

dealCards_WaitId = nil
function dealCardsDelayed()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the nicest way I've found to let the script spam calls to an expensive function, but only have the function be called once everything has settled down(1.5 seconds after the last request to call the function). The only problems with this kind of set-up is if the function triggers itself, or the game is so laggy that 1.5 seconds is not enough time for the calls to the function to stop.

if dealCards_WaitId != nil then
Wait.stop(dealCards_WaitId)
dealCards_WaitId = nil
end
dealCards_WaitId = Wait.time(dealCards,2.5,1)
end

-- Deals the cards on the board
function dealCards()
-- check all objects to delete unused codenames cards
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codenames keeps track of card guid's but this doesn't get saved or loaded when a rewind or host change happens. So let's just delete all codename cards that aren't in the tracked card guid list.(I do a similar thing for clue tiles later on when starting a new game)

for _, obj in pairs(getObjects()) do
if isCodenamesCard(obj) and not isCard(obj.guid) then
obj.destruct()
end
end
for i = 1, 25, 1 do
local cardObject = getObjectFromGUID(cards[i].guid)
if cards[i].guid == nil or cardObject == nil then
Expand Down Expand Up @@ -1063,14 +1090,17 @@ end

function onObjectDrop(color, agent)
-- Ensure that the dropped object is an agent card
if gameState.status ~= 1 or agent.tag ~= "Tile" or agents[agent.guid] == nil then
if gameState.status ~= 1 or agent.type ~= "Tile" or agents[agent.guid] == nil then
return
end

-- Find the closest card position to the dropped agent
local cardIndex = findClosestCard(1.65, agent.getPosition())
if cardIndex == nil or cards[cardIndex].covered then
-- Either no close card found, or card is already covered
agent.setAngularVelocity({0, 0, 0})
agent.setVelocity({0, 0, 0})
agent.setRotation({0, 180, 180})
agent.setPositionSmooth(agents[agent.guid].position)
return
end
Expand All @@ -1079,6 +1109,9 @@ function onObjectDrop(color, agent)
local agentColor = agents[agent.guid].color
local cardColor = cards[cardIndex].color
if cardColor ~= agentColor then
agent.setAngularVelocity({0, 0, 0})
agent.setVelocity({0, 0, 0})
agent.setRotation({0, 180, 180})
agent.setPositionSmooth(agents[agent.guid].position)
Player[color].broadcast("[a020f0]» [da1918]ERROR: [ffffff]An agent has been placed incorrectly. You placed a " .. agentColor .. " agent on a " .. cardColor .. " card. [a020f0]«")
return
Expand Down Expand Up @@ -1396,30 +1429,6 @@ function votePass(color)
playerVote(color, 26)
end

function onObjectPickUp(color, object)
-- Clues
if object.tag == "Card" and not object.spawning then
for i = 1, 25, 1 do
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved this section into onPlayerAction() so that it can add a vote without ever attempting to pick up the card for a frame.

if cards[i].guid == object.guid then
object.setVelocity({0, 0, 0})
object.drop()
object.setPosition({cards[i].position.x, 1.03, cards[i].position.z})
if #Player[color].getSelectedObjects() <= 1 then
playerVote(color, i)
end
break
end
end
elseif agents[object.guid] ~= nil then
-- Prevent picking up of agent tiles from anyone but red or blue
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section isn't ever used since a pickup action on an agent by a non-codemaster is stopped by a return of "false" inside onPlayerAction() when this happens.

if color ~= "Red" and color ~= "Blue" then
object.setVelocity({0, 0, 0})
object.drop()
object.setPosition({agents[object.guid].position.x, 1.01, agents[object.guid].position.z})
end
end
end

function onPlayerAction(player, action, objects)
local processAction = false
local actionsToProcess = {
Expand All @@ -1429,7 +1438,8 @@ function onPlayerAction(player, action, objects)
Player.Action.RotateOver,
Player.Action.FlipIncrementalLeft,
Player.Action.FlipIncrementalRight,
Player.Action.FlipOver
Player.Action.FlipOver,
Player.Action.Delete
}

for _, handledAction in ipairs(actionsToProcess) do
Expand All @@ -1442,6 +1452,24 @@ function onPlayerAction(player, action, objects)
if not processAction then
return
end

if action == Player.Action.Delete then
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is where I moved the onObjectDestroy() function so that it doesn't run when the script deletes a card.

-- if there is an ongoing game and a card is deleted then refresh the cards
for _, obj in pairs(objects) do
if gameState.status == 1 and isCodenamesCard(obj) then
-- Delete word cards and refresh them
dealCardsDelayed()
obj.destruct()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I take over what gets deleted in a player action. Deleting agents currently requires the game to be reloaded, so I prevent that outright. Deleting cards causes them to be redealt. And then it deletes everything else the player wanted to delete.

elseif agents[obj.guid] ~= nil then
-- Do not delete any agents
else
-- Delete any other objects in the action
obj.destruct()
end
end
-- Prevent deletion of selected objects as we have already delete only what was allowed
return false
end

local objectIncludesCard = false
local objectIncludesAgent = false
Expand Down Expand Up @@ -1475,6 +1503,27 @@ function onPlayerAction(player, action, objects)
elseif settings.cardTilting and gameState.status == 1 and not gameState.canVote then
errorMessage = "[a020f0]» [da1918]ERROR: [ffffff]You can't vote until you've been given a clue! [a020f0]«"
end
if #objects == 1 and objects[1].type == "Card" and not objects[1].spawning then
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is where I moved the onObjectPickUp() function so that I can use "return false" when a vote is cast.
I edit this in a later commit to entirely disallow picking up cards.(the only downside I see is that if the game physics somehow puts the card somewhere else, it can't easily be put back)

object = objects[1]
for i = 1, 25, 1 do
if cards[i].guid == object.guid then
if #player.getSelectedObjects() <= 1 then
playerVote(player.color, i)
end
return false
end
end
end
local hasCards = false
Copy link
Contributor Author

@Canonelis Canonelis Dec 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for any pickup action, it will prevent the pickup from happening if it contains any word cards, similar to before. As a nicety, it removes the cards from the player's selection so that the player can try and pick up the items again without being blocked.

for _, obj in pairs(objects) do
if isCard(obj.guid) then
obj.removeFromPlayerSelection(player.color)
hasCards = true
end
end
if hasCards then
return false
end
elseif action == Player.Action.RotateIncrementalLeft or action == Player.Action.RotateIncrementalRight or action == Player.Action.RotateOver then
if objectIncludesCard and not isPlayerTurn then
errorMessage = "[a020f0]» [da1918]ERROR: [ffffff]You can't tilt cards when it's not your turn! [a020f0]«"
Expand Down Expand Up @@ -2004,4 +2053,5 @@ function api_playerSessionEndCB(responseRaw)
analytics.sessions[steamID] = nil
end
end
end
end