able to play through a game without issues using the base set

This commit is contained in:
2026-05-01 18:01:34 -07:00
parent 5ff452ba2c
commit c9afd53ca7
10 changed files with 2895 additions and 614 deletions

View File

@@ -85,7 +85,7 @@ class Citizen(Card):
worker_count, gold_payout_on_turn, gold_payout_off_turn, strength_payout_on_turn, worker_count, gold_payout_on_turn, gold_payout_off_turn, strength_payout_on_turn,
strength_payout_off_turn, magic_payout_on_turn, magic_payout_off_turn, has_special_payout_on_turn, strength_payout_off_turn, magic_payout_on_turn, magic_payout_off_turn, has_special_payout_on_turn,
has_special_payout_off_turn, special_payout_on_turn, special_payout_off_turn, special_citizen, has_special_payout_off_turn, special_payout_on_turn, special_payout_off_turn, special_citizen,
expansion): expansion, is_flipped=False):
super().__init__() super().__init__()
self.citizen_id = citizen_id self.citizen_id = citizen_id
self.name = name self.name = name
@@ -108,6 +108,7 @@ class Citizen(Card):
self.special_payout_off_turn = special_payout_off_turn self.special_payout_off_turn = special_payout_off_turn
self.special_citizen = special_citizen self.special_citizen = special_citizen
self.expansion = expansion self.expansion = expansion
self.is_flipped = bool(is_flipped)
def get_special_payout_on_turn(self): def get_special_payout_on_turn(self):
return self.special_payout_on_turn return self.special_payout_on_turn
@@ -115,6 +116,7 @@ class Citizen(Card):
def to_dict(self): def to_dict(self):
base_dict = super().to_dict() base_dict = super().to_dict()
return {**base_dict, return {**base_dict,
"is_flipped": bool(getattr(self, "is_flipped", False)),
"citizen_id": self.citizen_id, "citizen_id": self.citizen_id,
"gold_cost": self.gold_cost, "gold_cost": self.gold_cost,
"roll_match1": self.roll_match1, "roll_match1": self.roll_match1,
@@ -164,12 +166,14 @@ class Citizen(Card):
special_payout_on_turn=dict_["special_payout_on_turn"], special_payout_on_turn=dict_["special_payout_on_turn"],
special_payout_off_turn=dict_["special_payout_off_turn"], special_payout_off_turn=dict_["special_payout_off_turn"],
special_citizen=dict_["special_citizen"], special_citizen=dict_["special_citizen"],
expansion=dict_["expansion"]) expansion=dict_["expansion"],
is_flipped=bool(dict_.get("is_flipped", False)))
class Domain(Card): class Domain(Card):
def __init__(self, domain_id, name, gold_cost, shadow_count, holy_count, soldier_count, worker_count, vp_reward, def __init__(self, domain_id, name, gold_cost, shadow_count, holy_count, soldier_count, worker_count, vp_reward,
has_activation_effect, has_passive_effect, passive_effect, activation_effect, text, expansion): has_activation_effect, has_passive_effect, passive_effect, activation_effect, text, expansion,
acquired_turn_number=None):
super().__init__() super().__init__()
self.domain_id = domain_id self.domain_id = domain_id
self.name = name self.name = name
@@ -185,6 +189,7 @@ class Domain(Card):
self.activation_effect = activation_effect self.activation_effect = activation_effect
self.text = text self.text = text
self.expansion = expansion self.expansion = expansion
self.acquired_turn_number = acquired_turn_number
def to_dict(self): def to_dict(self):
return { return {
@@ -208,7 +213,8 @@ class Domain(Card):
"passive_effect": self.passive_effect, "passive_effect": self.passive_effect,
"activation_effect": self.activation_effect, "activation_effect": self.activation_effect,
"text": self.text, "text": self.text,
"expansion": self.expansion "expansion": self.expansion,
"acquired_turn_number": getattr(self, "acquired_turn_number", None),
} }
@classmethod @classmethod
@@ -227,7 +233,8 @@ class Domain(Card):
passive_effect=dict_['passive_effect'], passive_effect=dict_['passive_effect'],
activation_effect=dict_['activation_effect'], activation_effect=dict_['activation_effect'],
text=dict_['text'], text=dict_['text'],
expansion=dict_['expansion'] expansion=dict_['expansion'],
acquired_turn_number=dict_.get('acquired_turn_number'),
) )

View File

@@ -54,11 +54,11 @@ The server will start on `http://localhost:8000`
```json ```json
{ {
"player_id": "...", "player_id": "...",
"action_type": "hire_citizen|buy_domain|slay_monster|act_on_required_action|roll_phase|harvest_phase|play_turn", "action_type": "hire_citizen|build_domain|slay_monster|act_on_required_action|roll_phase|harvest_phase|play_turn",
"citizen_id": 123, // for hire_citizen "citizen_id": 123, // for hire_citizen
"domain_id": 456, // for buy_domain "domain_id": 456, // for build_domain
"monster_id": 789, // for slay_monster "monster_id": 789, // for slay_monster
"gold_cost": 5, // for hire_citizen/buy_domain "gold_cost": 5, // for hire_citizen/build_domain
"strength_cost": 3, // for slay_monster "strength_cost": 3, // for slay_monster
"magic_cost": 0, // optional "magic_cost": 0, // optional
"action": "choose 1" // for act_on_required_action "action": "choose 1" // for act_on_required_action

View File

@@ -58,7 +58,7 @@ Common paths:
- `roll_phase()` rolls two dice and computes a sum - `roll_phase()` rolls two dice and computes a sum
- `harvest_phase()` pays out from owned starters/citizens for all players based on the roll - `harvest_phase()` pays out from owned starters/citizens for all players based on the roll
- `hire_citizen(...)`, `buy_domain(...)`, `slay_monster(...)` mutate board stacks and player resources - `hire_citizen(...)`, `build_domain(...)`, `slay_monster(...)` mutate board stacks and player resources
### “choose …” actions ### “choose …” actions

View File

@@ -45,7 +45,7 @@ Lobby cleanup:
Supported `action_type` values currently include: Supported `action_type` values currently include:
- `hire_citizen` - `hire_citizen`
- `buy_domain` - `build_domain`
- `slay_monster` - `slay_monster`
- `take_resource` - `take_resource`
- `harvest_card` - `harvest_card`

1940
game.py

File diff suppressed because it is too large Load Diff

90
game_models.py Normal file
View File

@@ -0,0 +1,90 @@
from cards import Citizen, Domain, Duke, Monster, Starter
class Player:
def __init__(self, player_id, name):
self.player_id = player_id
self.name = name
self.owned_starters = []
self.owned_citizens = []
self.owned_domains = []
self.owned_dukes = []
self.owned_monsters = []
self.gold_score = 2
self.strength_score = 0
self.magic_score = 1
self.victory_score = 0
self.is_first = False
self.shadow_count = 0
self.holy_count = 0
self.soldier_count = 0
self.worker_count = 0
self.effects = {
"roll_phase": [],
"harvest_phase": [],
"action_phase": [],
}
self.harvest_delta = {"gold": 0, "strength": 0, "magic": 0, "victory": 0}
@classmethod
def from_dict(cls, data):
player_id = data["player_id"]
name = data["name"]
player = cls(player_id, name)
player.owned_starters = [Starter.from_dict(s) for s in data["owned_starters"]]
player.owned_citizens = [Citizen.from_dict(c) for c in data["owned_citizens"]]
player.owned_domains = [Domain.from_dict(d) for d in data["owned_domains"]]
player.owned_dukes = [Duke.from_dict(d) for d in data["owned_dukes"]]
player.owned_monsters = [Monster.from_dict(m) for m in data["owned_monsters"]]
player.gold_score = data["gold_score"]
player.strength_score = data["strength_score"]
player.magic_score = data["magic_score"]
player.victory_score = data["victory_score"]
player.is_first = data["is_first"]
player.effects = data["effects"]
player.harvest_delta = data.get("harvest_delta", {"gold": 0, "strength": 0, "magic": 0, "victory": 0})
roles = player.calc_roles()
player.shadow_count = roles["shadow_count"]
player.holy_count = roles["holy_count"]
player.soldier_count = roles["soldier_count"]
player.worker_count = roles["worker_count"]
return player
def calc_roles(self):
shadow_count = 0
holy_count = 0
soldier_count = 0
worker_count = 0
for citizen in self.owned_citizens:
shadow_count = shadow_count + citizen.shadow_count
holy_count = holy_count + citizen.holy_count
soldier_count = soldier_count + citizen.soldier_count
worker_count = worker_count + citizen.worker_count
for domain in self.owned_domains:
shadow_count = shadow_count + domain.shadow_count
holy_count = holy_count + domain.holy_count
soldier_count = soldier_count + domain.soldier_count
worker_count = worker_count + domain.worker_count
roles_dict = {
"shadow_count": shadow_count,
"holy_count": holy_count,
"soldier_count": soldier_count,
"worker_count": worker_count,
}
return roles_dict
class LobbyMember:
def __init__(self, player_name, player_id):
self.name = player_name
self.player_id = player_id
self.is_ready = False
self.debug_starting_resources = False
self.last_active_time = 0
class GameMember:
def __init__(self, player_id, player_name, game_id):
self.name = player_name
self.player_id = player_id
self.game_id = game_id

111
game_serialization.py Normal file
View File

@@ -0,0 +1,111 @@
from json import JSONEncoder
from cards import Citizen, Domain, Duke, Monster, Starter
from game_models import GameMember, LobbyMember, Player
class SummaryEncoder(JSONEncoder):
def default(self, obj):
if isinstance(obj, Player):
return {
"player_id": obj.player_id,
"name": obj.name,
"owned_citizens": len(obj.owned_citizens),
"owned_domains": len(obj.owned_domains),
"owned_monsters": len(obj.owned_monsters),
"gold_score": obj.gold_score,
"strength_score": obj.strength_score,
"magic_score": obj.magic_score,
"victory_score": obj.victory_score,
"is_first": obj.is_first,
}
if isinstance(obj, LobbyMember):
return {
"player_name": obj.name,
"player_id": obj.player_id,
"is_ready": obj.is_ready,
}
if isinstance(obj, GameMember):
return {
"player_name": obj.name,
"player_id": obj.player_id,
}
if hasattr(obj, "game_id") and hasattr(obj, "player_list"):
return {
"game_id": obj.game_id,
"player_list": obj.player_list,
}
return super().default(obj)
class GameObjectEncoder(JSONEncoder):
def default(self, obj):
if isinstance(obj, Player):
roles = obj.calc_roles()
return {
"player_id": obj.player_id,
"name": obj.name,
"owned_starters": [starter.to_dict() for starter in obj.owned_starters],
"owned_citizens": [citizen.to_dict() for citizen in obj.owned_citizens],
"owned_domains": [domain.to_dict() for domain in obj.owned_domains],
"owned_dukes": [duke.to_dict() for duke in obj.owned_dukes],
"owned_monsters": [monster.to_dict() for monster in obj.owned_monsters],
"gold_score": obj.gold_score,
"strength_score": obj.strength_score,
"magic_score": obj.magic_score,
"victory_score": obj.victory_score,
"is_first": obj.is_first,
"shadow_count": roles["shadow_count"],
"holy_count": roles["holy_count"],
"soldier_count": roles["soldier_count"],
"worker_count": roles["worker_count"],
"effects": obj.effects,
"harvest_delta": getattr(obj, "harvest_delta", {"gold": 0, "strength": 0, "magic": 0, "victory": 0}),
}
if isinstance(obj, Duke):
return obj.to_dict()
if isinstance(obj, Monster):
return obj.to_dict()
if isinstance(obj, Starter):
return obj.to_dict()
if isinstance(obj, Citizen):
return obj.to_dict()
if isinstance(obj, Domain):
return obj.to_dict()
if hasattr(obj, "game_id") and hasattr(obj, "player_list") and hasattr(obj, "monster_grid"):
ca_raw = getattr(obj, "concurrent_action", None)
ca_enc = ca_raw
if isinstance(ca_raw, dict) and not (ca_raw.get("pending") or []):
ca_enc = None
return {
"game_id": obj.game_id,
"player_list": obj.player_list,
"monster_grid": obj.monster_grid,
"citizen_grid": obj.citizen_grid,
"domain_grid": obj.domain_grid,
"die_one": obj.die_one,
"die_two": obj.die_two,
"die_sum": obj.die_sum,
"rolled_die_one": getattr(obj, "rolled_die_one", obj.die_one),
"rolled_die_two": getattr(obj, "rolled_die_two", obj.die_two),
"rolled_die_sum": getattr(obj, "rolled_die_sum", obj.die_sum),
"pending_roll": getattr(obj, "pending_roll", None),
"exhausted_count": obj.exhausted_count,
"effects": obj.effects,
"action_required": obj.action_required,
"pending_required_choice": getattr(obj, "pending_required_choice", None),
"pending_action_end_queue": getattr(obj, "pending_action_end_queue", None) or [],
"concurrent_action": ca_enc,
"tick_id": getattr(obj, "tick_id", 0),
"turn_number": getattr(obj, "turn_number", 1),
"turn_index": getattr(obj, "turn_index", 0),
"phase": getattr(obj, "phase", "roll"),
"actions_remaining": getattr(obj, "actions_remaining", 0),
"active_player_id": obj.current_player_id() if hasattr(obj, "current_player_id") else None,
"harvest_player_order": getattr(obj, "harvest_player_order", None),
"harvest_player_idx": getattr(obj, "harvest_player_idx", 0),
"harvest_consumed": getattr(obj, "harvest_consumed", {}) or {},
"harvest_prompt_slots": obj.harvest_slots_for_api() if hasattr(obj, "harvest_slots_for_api") else [],
"game_log": list(getattr(obj, "game_log", None) or []),
}
return super().default(obj)

283
game_setup.py Normal file
View File

@@ -0,0 +1,283 @@
import random
from typing import List
from cards import Citizen, Domain, Duke, Monster, Starter
from game_models import Player
def load_game_data(game_id, preset, player_list_from_lobby, debug_starting_resources=False):
import mariadb
monster_query = ""
monster_stack = []
citizen_query = ""
citizen_stack = []
domain_query = "select_random_domains"
domain_stack = []
duke_query = "select_random_dukes"
duke_stack = []
starter_query = "SELECT * FROM starters"
starter_stack = []
player_list = []
citizen_grid: List[List[Citizen]] = [[] for _ in range(10)]
domain_grid: List[List[Domain]] = [[] for _ in range(5)]
monster_grid: List[List[Monster]] = [[] for _ in range(5)]
die_one = 0
die_two = 0
die_sum = 0
exhausted_count = 0
effects = {
"roll_phase": [],
"harvest_phase": [],
"action_phase": [],
}
action_required = {
"id": "",
"action": "",
}
tick_id = 0
turn_number = 1
turn_index = 0
# Start in setup; if no setup actions are needed the engine will advance into roll.
phase = "setup"
actions_remaining = 0
harvest_processed = False
pending_harvest_choices = []
match preset:
case "base1":
monster_query = "select_base1_monsters"
citizen_query = "select_base1_citizens"
case "base2":
monster_query = "select_base2_monsters"
citizen_query = "select_base2_citizens"
try:
my_connect = mariadb.connect(
user="vckonline", password="vckonline", host="127.0.0.1", database="vckonline", port=3306
)
my_cursor = my_connect.cursor(dictionary=True)
my_cursor.callproc(monster_query)
results = my_cursor.fetchall()
for row in results:
my_monster = Monster(
row["id_monsters"],
row["name"],
row["area"],
row["monster_type"],
row["monster_order"],
row["strength_cost"],
row["magic_cost"],
row["vp_reward"],
row["gold_reward"],
row["strength_reward"],
row["magic_reward"],
row["has_special_reward"],
row["special_reward"],
row["has_special_cost"],
row["special_cost"],
row["is_extra"],
row["expansion"],
)
monster_stack.append(my_monster)
my_cursor.callproc(citizen_query)
citizen_count = 5
if len(player_list_from_lobby) == 5:
citizen_count = 6
results = my_cursor.fetchall()
for row in results:
for _ in range(citizen_count):
my_citizen = Citizen(
row["id_citizens"],
row["name"],
row["gold_cost"],
row["roll_match1"],
row["roll_match2"],
row["shadow_count"],
row["holy_count"],
row["soldier_count"],
row["worker_count"],
row["gold_payout_on_turn"],
row["gold_payout_off_turn"],
row["strength_payout_on_turn"],
row["strength_payout_off_turn"],
row["magic_payout_on_turn"],
row["magic_payout_off_turn"],
row["has_special_payout_on_turn"],
row["has_special_payout_off_turn"],
row["special_payout_on_turn"],
row["special_payout_off_turn"],
row["special_citizen"],
row["expansion"],
)
citizen_stack.append(my_citizen)
my_cursor.callproc(domain_query)
results = my_cursor.fetchall()
for row in results:
my_domain = Domain(
row["id_domains"],
row["name"],
row["gold_cost"],
row["shadow_count"],
row["holy_count"],
row["soldier_count"],
row["worker_count"],
row["vp_reward"],
row["has_activation_effect"],
row["has_passive_effect"],
row["passive_effect"],
row["activation_effect"],
row["text"],
row["expansion"],
)
domain_stack.append(my_domain)
my_cursor.callproc(duke_query)
results = my_cursor.fetchall()
for row in results:
my_duke = Duke(
row["id_dukes"],
row["name"],
row["gold_mult"],
row["strength_mult"],
row["magic_mult"],
row["shadow_mult"],
row["holy_mult"],
row["soldier_mult"],
row["worker_mult"],
row["monster_mult"],
row["citizen_mult"],
row["domain_mult"],
row["boss_mult"],
row["minion_mult"],
row["beast_mult"],
row["titan_mult"],
row["expansion"],
)
duke_stack.append(my_duke)
my_cursor.execute(starter_query)
my_result = my_cursor.fetchall()
for row in my_result:
my_starter = Starter(
row["id_starters"],
row["name"],
row["roll_match1"],
row["roll_match2"],
row["gold_payout_on_turn"],
row["gold_payout_off_turn"],
row["strength_payout_on_turn"],
row["strength_payout_off_turn"],
row["magic_payout_on_turn"],
row["magic_payout_off_turn"],
row["has_special_payout_on_turn"],
row["has_special_payout_off_turn"],
row["special_payout_on_turn"],
row["special_payout_off_turn"],
row["expansion"],
)
starter_stack.append(my_starter)
my_cursor.close()
my_connect.close()
except Exception as e:
print(f"Error: {e}")
# create players and determine order
if not all([player_list_from_lobby, starter_query, monster_stack, citizen_stack, domain_stack, duke_stack]):
raise ValueError("One or more required lists are empty.")
else:
for player in player_list_from_lobby:
my_player = Player(player.player_id, player.name)
if debug_starting_resources:
my_player.gold_score = 100
my_player.strength_score = 100
my_player.magic_score = 100
player_list.append(my_player)
random.shuffle(player_list)
player_list[0].is_first = True
# give players starters and dukes
for player in player_list:
player.owned_starters.append(starter_stack[0])
player.owned_starters.append(starter_stack[1])
for _ in range(2):
player.owned_dukes.append(duke_stack.pop())
# deal monsters onto the board
grouped_monsters = {}
for monster in monster_stack:
area = monster.area
if area in grouped_monsters:
grouped_monsters[area].append(monster)
else:
grouped_monsters[area] = [monster]
for area, monsters in grouped_monsters.items():
monsters.sort(key=lambda item: item.order, reverse=True)
areas = list(grouped_monsters.keys())
chosen_areas = random.sample(areas, 5)
for i, area in enumerate(chosen_areas):
monsters = grouped_monsters[area]
monster_grid[i].extend(monsters)
for stack in monster_grid:
for monster in stack:
monster.toggle_visibility(True)
stack[-1].toggle_accessibility(True)
# deal citizens onto the board
citizens_by_roll = {roll: [] for roll in [1, 2, 3, 4, 5, 6, 7, 8, 9, 11]}
for citizen in citizen_stack:
citizen.toggle_visibility()
citizens_by_roll[citizen.roll_match1].append(citizen)
for roll in citizens_by_roll:
index = roll - 1 if roll < 11 else 9
citizens = citizens_by_roll[roll]
citizen_grid[index].extend(list(citizens))
citizen_grid[index][-1].toggle_accessibility(True)
if debug_starting_resources:
for player in player_list:
for stack in citizen_grid:
c = stack.pop(-1)
c.is_flipped = False
c.toggle_visibility(True)
c.toggle_accessibility(True)
player.owned_citizens.append(c)
if stack:
stack[-1].toggle_accessibility(True)
# deal the domains into stacks
for i in range(5):
stack = domain_grid[i]
for j in range(3):
if j == 2:
domain = domain_stack.pop()
domain.toggle_visibility(True)
domain.toggle_accessibility(True)
stack.append(domain)
else:
domain = domain_stack.pop()
stack.append(domain)
game_state = {
"game_id": game_id,
"player_list": player_list,
"monster_grid": monster_grid,
"citizen_grid": citizen_grid,
"domain_grid": domain_grid,
"die_one": die_one,
"die_two": die_two,
"die_sum": die_sum,
"exhausted_count": exhausted_count,
"effects": effects,
"action_required": action_required,
"concurrent_action": None,
"tick_id": tick_id,
"turn_number": turn_number,
"turn_index": turn_index,
"phase": phase,
"actions_remaining": actions_remaining,
"harvest_processed": harvest_processed,
"pending_harvest_choices": pending_harvest_choices,
"harvest_player_order": None,
"harvest_player_idx": 0,
"harvest_consumed": {},
"game_log": [],
"pending_action_end_queue": [],
}
return game_state

1026
server.py

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +1,17 @@
INSERT INTO vckonline.domains (name,gold_cost,shadow_count,holy_count,soldier_count,worker_count,vp_reward,has_activation_effect,has_passive_effect,passive_effect,activation_effect,`text`,image) VALUES INSERT INTO vckonline.domains (name,gold_cost,shadow_count,holy_count,soldier_count,worker_count,vp_reward,has_activation_effect,has_passive_effect,passive_effect,activation_effect,`text`,image) VALUES
('Jousting Field',13,1,0,1,1,3,0,1,NULL,NULL,'During your Harvest Phase, gain 1gp * Knight you own.',NULL), ('Jousting Field',13,1,0,1,1,3,0,1,'harvest:gain_per_owned_citizen_name Knight g 1',NULL,'During your Harvest Phase, gain 1gp * Knight you own.',NULL),
('Ancient Tomb',7,0,1,1,1,3,1,0,NULL,NULL,'Immediately add 3sp to a Monster Strength value.',NULL), ('Ancient Tomb',7,0,1,1,1,3,1,0,NULL,'activation_monster_strength +3','Immediately add 3sp to a Monster Strength value.',NULL),
('Foxgrove Palisade',9,1,0,1,0,3,0,1,NULL,NULL,'During your Roll Phase, you may pay 2gp to change one die to equal 6.',NULL), ('Foxgrove Palisade',9,1,0,1,0,3,0,1,'roll:set_one_die target=6 cost=g:2',NULL,'During your Roll Phase, you may pay 2gp to change one die to equal 6.',NULL),
('The Desert Orchid',9,1,1,0,0,3,0,1,NULL,NULL,'During your Roll Phase, you may pay 1gp * owned Holy Citizens to change one die to equal 1.',NULL), ('The Desert Orchid',9,1,1,0,0,3,0,1,'roll:set_one_die target=1 cost=g_per_owned_role:holy_citizen',NULL,'During your Roll Phase, you may pay 1gp * owned Holy Citizens to change one die to equal 1.',NULL),
('Pretorius Conclave',8,1,1,1,1,2,1,0,NULL,NULL,'Immediately gain a Citizen from the center stacks.',NULL), ('Pretorius Conclave',8,1,1,1,1,2,1,0,NULL,'choose <citizens>','Immediately gain a Citizen from the center stacks.',NULL),
('Emerald Stronghold',12,0,1,1,1,5,0,1,NULL,NULL,'During your Action phase, ignore ''+'' when buying Citizens.',NULL), ('Emerald Stronghold',12,0,1,1,1,5,0,1,'effect:add action:emeraldstronghold',NULL,'During your Action phase, ignore ''+'' when buying Citizens.',NULL),
('Pratchett''s Plateau',8,0,1,0,1,3,0,1,NULL,NULL,'During your Action phase, Domains cost you 1gp less to buy.',NULL), ('Pratchett''s Plateau',8,0,1,0,1,3,0,1,'effect:add action:pratchettsplateau',NULL,'During your Action phase, Domains cost you 1gp less to build.',NULL),
('Shelly Commons',13,0,1,1,1,4,0,1,NULL,NULL,'At the end of your Action phase, pay 1gp to a Player of your choice to gain 1vp.',NULL), ('Shelley Commons',13,0,1,1,1,4,0,1,'action:end manipulate_resources mode=pay_to_player gain=vp:1 pay=g:1 optional=true',NULL,'At the end of your Action phase, pay 1gp to a Player of your choice to gain 1vp.',NULL),
('Cathedral of St Aquila',8,0,2,0,0,3,0,1,NULL,NULL,'At the end of your Action phase, take 1gp from a Player of your choice.',NULL), ('Cathedral of St Aquila',8,0,2,0,0,3,0,1,'action:end manipulate_resources mode=take_from_player take=g:1 optional=true',NULL,'At the end of your Action phase, take 1gp from a Player of your choice.',NULL),
('Cursed Cavern',10,1,1,0,1,2,1,0,NULL,NULL,'All players immediately flip a Citizen and you gain 4mp.',NULL); ('Cursed Cavern',10,1,1,0,1,2,1,0,NULL,'m 4 + concurrent_flip_one_citizen','All players immediately flip a Citizen and you gain 4mp.',NULL);
INSERT INTO vckonline.domains (name,gold_cost,shadow_count,holy_count,soldier_count,worker_count,vp_reward,has_activation_effect,has_passive_effect,passive_effect,activation_effect,`text`,image) VALUES INSERT INTO vckonline.domains (name,gold_cost,shadow_count,holy_count,soldier_count,worker_count,vp_reward,has_activation_effect,has_passive_effect,passive_effect,activation_effect,`text`,image) VALUES
('Darktide Harbour',6,1,0,1,1,2,1,0,NULL,NULL,'Immediately gain a Shadow Citizen from the center stacks.',NULL), ('Darktide Harbour',6,1,0,1,1,2,1,0,NULL,'choose <citizens where role==shadow>','Immediately gain a Shadow Citizen from the center stacks.',NULL),
('King Tower',12,0,1,0,2,3,0,1,NULL,NULL,'At the end of your Action Phase, pay 1mp to a Player of your choice to gain 1vp.',NULL), ('King Tower',12,0,1,0,2,3,0,1,'action:end manipulate_resources mode=pay_to_player gain=vp:1 pay=m:1 optional=true',NULL,'At the end of your Action Phase, pay 1mp to a Player of your choice to gain 1vp.',NULL),
('Cloudrider''s Camp',8,0,1,1,0,2,1,0,NULL,NULL,'Immediately gain 3sp and a Soldier Citizen worth 2gp or less.',NULL), ('Cloudrider''s Camp',8,0,1,1,0,2,1,0,NULL,'s 3 + choose <citizens where role==soldier and gold_cost<=2>','Immediately gain 3sp and a Soldier Citizen worth 2gp or less.',NULL),
('The Orb of Urdr',6,1,1,0,0,1,0,1,NULL,NULL,'At the end of your Action Phase, take 1mp from a Player of your choice.',NULL), ('The Orb of Urdr',6,1,1,0,0,1,0,1,'action:end manipulate_resources mode=take_from_player take=m:1 optional=true',NULL,'At the end of your Action Phase, take 1mp from a Player of your choice.',NULL),
('Wisborg',6,1,0,0,2,3,1,0,NULL,NULL,'You may immediately pay 3gp to gain 3vp.',NULL); ('Wisborg',6,1,0,0,2,3,1,0,NULL,'manipulate_resources mode=self_convert pay=g:3 gain=vp:3 optional=true','You may immediately pay 3gp to gain 3vp.',NULL);