-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
5f9859f
commit fbd39d5
Showing
6 changed files
with
865 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,21 @@ | ||
# villageoflegends | ||
Tabletop Simulator Workshop for Village of Legends boardgame | ||
# Village of Legends - Steam TTS workshop | ||
![background](imgs/background.jpg) | ||
Tabletop Simulator Workshop for [Village of Legends boardgame](https://www.pelotology.com). | ||
|
||
You can: | ||
|
||
- buy the physical game [here](https://www.pelotology.com/shop) | ||
- find the rules of the game [here](https://www.pelotology.com/rules) | ||
- watch a gameplay of the base game [here](https://www.youtube.com/watch?v=hs4qvprbbqg) | ||
- watch a video about rules of the base game [here](https://www.youtube.com/watch?v=WD-RvsNV3Ls) | ||
- watch a gameplay of the last expansion [here](https://www.youtube.com/watch?v=Ukg3ufXSlao) | ||
|
||
The workshop can be found [here on Steam](https://steamcommunity.com/sharedfiles/filedetails/?id=2472269863) | ||
|
||
# Lua source code | ||
The game has been created using [Tabletop Simulator API](https://api.tabletopsimulator.com/). | ||
The source code is in [Lua](https://www.lua.org/pil/1.html) and it is mainly composed by 3 files: | ||
|
||
- `Global`: code related to the global environment of the game, handling the players placement and their playing boards and starting decks. | ||
- `Market`: code related to all the market automation, like the shuffling of the main decks and the first deal of the cards of the market. There is also a refill button to add a new card once an item is bought. | ||
- `Player Board`: code related to the player board automation, like the life points counters and the random selection of a character. |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
--[[ Lua code. See documentation: https://api.tabletopsimulator.com/ --]] | ||
Market = getObjectFromGUID('33133e') | ||
Players = {} | ||
|
||
|
||
function SetupPlayers(num_players) | ||
UI.hide('welcome_panel') | ||
PlayersSelected = true | ||
|
||
-- declare a table of all possible players | ||
local colors = Player.getAvailableColors() | ||
|
||
-- sort players prioritizing the seated ones | ||
table.sort(colors, function(a, b) return Player[a].seated end) | ||
|
||
-- set the starting position | ||
local initial_position = { | ||
position = Vector(0, 5, -50), | ||
rotation = Vector(0, 360, 0), | ||
scale = Vector(12.00, 12.00, 7.00), | ||
} | ||
|
||
-- starting board and deck available and moved on the side | ||
local board = getObjectFromGUID('4bec09') | ||
board.setPosition({-40.00, 1.00, 20.00}) | ||
local starting_deck = getObjectFromGUID('fc501f') | ||
starting_deck.setPosition({-40.00, 1.00, 18.00}) | ||
|
||
local angle_step = (num_players%2==0 and 180 or 120) / num_players | ||
local vertical_offset = 20 | ||
|
||
-- loop through the player hands, relocating them based on the number of players | ||
local fixed_players = 0 | ||
for i, color in ipairs(colors) do | ||
if fixed_players < num_players then | ||
local swap_direction = i%2==0 and 1 or -1 | ||
-- the final result should be a displacement of (-2, -1, 0, 1, 2) or (-2, -1, 0, 0, 1, 2) | ||
local player_ratio = num_players%2==0 and ( | ||
i%2==0 and math.floor((i-1)/2) or math.floor(i/2)) or ( -- if players are even | ||
math.floor(i/2) -- if players are odd | ||
) | ||
|
||
local rotation_angle = 0 -- player_ratio * swap_direction * angle_step | ||
local horizontal_offset = -swap_direction * (num_players%2==0 and 5 or 2) | ||
local rotate_over_angle = player_ratio * swap_direction * angle_step | ||
|
||
-- horrible way to handle special cases | ||
if num_players%2==0 then | ||
if i==1 or i==2 then | ||
horizontal_offset = -swap_direction*10 | ||
end | ||
if (i==3 or i==4) and num_players==4 then | ||
horizontal_offset = swap_direction*5 | ||
end | ||
if i==5 or i==6 then | ||
horizontal_offset = swap_direction*2 | ||
end | ||
end | ||
|
||
-- print(tostring(i) .. ' ' .. tostring(horizontal_offset)) | ||
|
||
-- move the hand-zone and the player board with all details based on the rotation | ||
local transform = { | ||
position = initial_position.position:copy():rotateOver('y', rotate_over_angle):add(Vector(horizontal_offset, 0, vertical_offset)), | ||
rotation = initial_position.rotation:copy():add(Vector(0, rotation_angle, 0)), | ||
scale = initial_position.scale | ||
} | ||
Player[color].setHandTransform(transform) | ||
fixed_players = fixed_players + 1 | ||
|
||
-- get a copy of the board | ||
local current_board = board.clone() | ||
local board_position = transform.position:add(Vector(0,0,10)) --moveTowards(Vector(0, 0, 20), 10.0) | ||
current_board.setPosition({board_position[1], 1.0, board_position[3]}) | ||
-- current_board.setRotation(transform.rotation:add(Vector(0, 180, 0))) | ||
|
||
-- get a copy of the starting deck | ||
local current_deck = starting_deck.clone() | ||
current_deck.shuffle() | ||
local deck_position = board_position:copy():add(current_deck.positionToWorld(Vector(2.43, 0.0, -1.69))) | ||
current_deck.setPosition({deck_position[1], 1.0, deck_position[3]}) | ||
|
||
else | ||
Player[color].setHandTransform({ | ||
position = initial_position.position, | ||
rotation = initial_position.rotation, | ||
scale = initial_position.scale, | ||
}) | ||
end | ||
end | ||
board.destruct() | ||
starting_deck.destruct() | ||
end | ||
|
||
function TwoPlayers() SetupPlayers(2) end | ||
function ThreePlayers() SetupPlayers(3) end | ||
function FourPlayers() SetupPlayers(4) end | ||
function FivePlayers() SetupPlayers(5) end | ||
function SixPlayers() SetupPlayers(6) end | ||
|
||
|
||
function onSave() | ||
if GameStarted then -- worth to save only if game is started | ||
local data_to_save = { | ||
game_started = GameStarted, | ||
characters = Characters | ||
} | ||
SavedData = JSON.encode(data_to_save) | ||
return SavedData | ||
end | ||
end | ||
|
||
function onLoad(saved_data) | ||
if saved_data ~= '' then | ||
local loaded_data = JSON.decode(saved_data) | ||
PlayersSelected = loaded_data.game_started | ||
MarketStarted = loaded_data.game_started | ||
GameStarted = loaded_data.game_started | ||
Characters = loaded_data.characters | ||
else | ||
PlayersSelected = false -- if set, the players tiles are selected and placed on the playerboards | ||
MarketStarted = false -- if set, the market is ready to be used | ||
GameStarted = false -- if set, the game is ready to be played | ||
Characters = { | ||
Barbarian = {current_life_points = 45, max_life = 45, selected=false, tile_guid='5e201a'}, | ||
Dwarf = {current_life_points = 45, max_life = 45, selected=false, tile_guid='7e5c84'}, | ||
Elf = {current_life_points = 35, max_life = 35, selected=false, tile_guid='d24fae'}, | ||
Mage = {current_life_points = 30, max_life = 30, selected=false, tile_guid='29a29b'}, | ||
Warrior = {current_life_points = 40, max_life = 40, selected=false, tile_guid='1cbcd9'}, | ||
Paladin = {current_life_points = 40, max_life = 40, selected=false, tile_guid='ebc562'}, | ||
Cleric = {current_life_points = 35, max_life = 35, selected=false, tile_guid='5c2f72'}, | ||
Druid = {current_life_points = 30, max_life = 30, selected=false, tile_guid='a11911'}, | ||
Thief = {current_life_points = 35, max_life = 35, selected=false, tile_guid='f8b21a'}, | ||
Slayer = {current_life_points = 40, max_life = 40, selected=false, tile_guid='0c6550'}, | ||
DarkElf = {current_life_points = 35, max_life = 35, selected=false, tile_guid='0a23bf'}, | ||
RedMage = {current_life_points = 35, max_life = 35, selected=false, tile_guid='79e8f1'}, | ||
Necromancer = {current_life_points = 30, max_life = 30, selected=false, tile_guid='d3a57c'}, | ||
} | ||
end | ||
if not GameStarted then | ||
UI.show('welcome_panel') | ||
end | ||
end | ||
|
||
--[[ The onUpdate event is called once per frame. --]] | ||
function onUpdate() | ||
-- should happen only once when START button is pressed. | ||
if MarketStarted and not GameStarted then | ||
GameStarted = true | ||
local tokens_zone = getObjectFromGUID('1e4e11') | ||
local index = 0 | ||
for _, character in pairs(Characters) do | ||
local character_tile = getObjectFromGUID(character.tile_guid) | ||
local token = nil | ||
-- search for token | ||
for _, object in ipairs(tokens_zone.getObjects()) do | ||
if object.getName() == character_tile.getName() then | ||
token = object | ||
break | ||
end | ||
end | ||
if character.selected then -- move the characters tokens | ||
character_tile.setLock(true) | ||
token.setPosition(character_tile.getPosition():copy():add(Vector(-2.95, 1, -2.95))) | ||
else -- move the unused ones | ||
index = index + 1 | ||
character_tile.setPosition({-50.00, 1.00 + index, 32.00}) | ||
token.setPosition({-48.00, 1.00 + index, 36.00}) | ||
end | ||
end | ||
tokens_zone.destruct() | ||
-- move characters picker out | ||
getObjectFromGUID('e6589a').setPosition({-40.00, 1.00, 32.00}) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
<!-- Xml UI. See documentation: https://api.tabletopsimulator.com/ui/introUI/ --> | ||
|
||
<!-- Welcome popup --> | ||
|
||
<Panel | ||
id="welcome_panel" | ||
height="600" | ||
width="900" | ||
color="rgba(0,0,0,1.0)" | ||
hideAnimation="FadeOut" | ||
animationDuration="0.5" | ||
allowDragging="true" | ||
returnToOriginalPositionWhenReleased="false" | ||
visibility="hidden" | ||
> | ||
<Image | ||
image="intro" | ||
preserveAspect="true" | ||
height="600" | ||
width="880" | ||
rectAlignment="UpperCenter" | ||
offsetXY="0 -20" | ||
> | ||
</Image> | ||
|
||
<HorizontalLayout | ||
id="rollerLayout" | ||
rectAlignment="LowerCenter" | ||
height="50" | ||
width="800" | ||
offsetXY="0 20" | ||
> | ||
<Text | ||
text="Choose number of players" | ||
color="white" | ||
outline="black" | ||
outlineSize="2 2" | ||
fontSize="30" | ||
></Text> | ||
<Button text="2" id="2" fontSize="30" onClick="TwoPlayers"></Button> | ||
<Button text="3" id="3" fontSize="30" onClick="ThreePlayers"></Button> | ||
<Button text="4" id="4" fontSize="30" onClick="FourPlayers"></Button> | ||
<Button text="5" id="5" fontSize="30" onClick="FivePlayers"></Button> | ||
<Button text="6" id="6" fontSize="30" onClick="SixPlayers"></Button> | ||
</HorizontalLayout> | ||
</Panel> |
Oops, something went wrong.