diff options
author | Frans Bongers <fransbongers@franss-mbp.home> | 2024-12-02 21:46:43 +0100 |
---|---|---|
committer | Frans Bongers <fransbongers@franss-mbp.home> | 2024-12-02 21:46:43 +0100 |
commit | 48726dd19ad8dde11a6172f30b5071987b7d09b5 (patch) | |
tree | 8b36ebc15d609d1a316ef6f98870d232ab09db6e /rules.ts | |
parent | e32cc3efe1c1ef5378cef422555e8c8289449938 (diff) | |
download | land-and-freedom-48726dd19ad8dde11a6172f30b5071987b7d09b5.tar.gz |
add tableaus and icon data
Diffstat (limited to 'rules.ts')
-rw-r--r-- | rules.ts | 609 |
1 files changed, 384 insertions, 225 deletions
@@ -11,6 +11,7 @@ import { LeafNode, Player, PlayerCard, + SeqNode, States, View, } from './types'; @@ -24,13 +25,20 @@ import data, { // MORALE_BONUS, // TEAMWORK_BONUS, // OFF, - ON, LIBERTY, + CLOSEST_TO_DEFEAT, + CLOSEST_TO_VICTORY, COLLECTIVIZATION, GOVERNMENT, SOVIET_SUPPORT, FOREIGN_AID, + ON, PLAYER_WITH_MOST_HERO_POINTS, + SELF, + ANY, + TEAMWORK_BONUS, + MORALE_BONUS, + OFF, // StaticData, // PLAYER_WITH_MOST_HERO_POINTS, } from './data'; @@ -67,20 +75,24 @@ const player_faction_map: Record<Player, FactionId> = { }; const front_names: Record<string, string> = { - a: 'the Aragon Front', - m: 'the Madrid Front', - n: 'the Nothern Front', - s: 'the Southern Front', + a: 'Aragon Front', + m: 'Madrid Front', + n: 'Nothern Front', + s: 'Southern Front', d: 'the Front closest to Defeat', v: 'the Front closest to Victory', }; +const bonus_names: string[] = ['Morale Bonus', 'Teamwork Bonus']; + const { cards, // fronts, tracks, } = data; +const bonuses = [MORALE_BONUS, TEAMWORK_BONUS]; + const faction_cards = { [ANARCHISTS_ID]: make_list(37, 54) as CardId[], [COMMUNISTS_ID]: make_list(19, 36) as CardId[], @@ -108,10 +120,18 @@ function gen_action(action: string, argument?: number | string) { } } +function gen_action_bonus(bonus_id: number) { + gen_action('bonus', bonus_id); +} + function gen_action_card(card_id: CardId) { gen_action('card', card_id); } +function gen_action_front(front_id: string) { + gen_action('front', front_id); +} + function gen_action_standee(track_id: number) { gen_action('standee', track_id); } @@ -150,18 +170,40 @@ const seq_node = 's'; const function_node = 'f'; const resolved = 1; +function create_leaf_node( + state: string, + faction: FactionId, + args?: any +): LeafNode { + return { + t: leaf_node, + s: state, + p: faction, + a: args, + r: 0, + }; +} + +function create_function_node(func_name: string, args?: any): FunctionNode { + return { + t: function_node, + f: func_name, + a: args, + r: 0, + }; +} + +function create_seq_node(children: EngineNode[]): SeqNode { + return { + t: seq_node, + c: children, + }; +} + function setup_bag_of_glory() { - console.log('setup_bag_of_glory'); game.engine = [ - { - t: leaf_node, - p: game.initiative, - s: 'add_glory', - }, - { - t: function_node, - f: 'end_of_turn', - }, + create_leaf_node('add_glory', game.initiative), + create_function_node('end_of_turn'), ]; next(); } @@ -169,43 +211,26 @@ function setup_bag_of_glory() { function setup_choose_card() { console.log('setup_choose_card'); const player_order = get_player_order(); - game.engine = player_order.map((faction_id) => ({ - t: leaf_node, - p: faction_id, - s: 'choose_card', - })); - game.engine.push({ - t: function_node, - f: 'setup_player_turn', - }); + game.engine = player_order.map((faction_id) => + create_leaf_node('choose_card', faction_id) + ); + game.engine.push(create_function_node('setup_player_turn')); next(); } function setup_player_turn() { console.log('setup_player_turn'); const player_order = get_player_order(); - game.engine = player_order.map((faction_id) => ({ - t: seq_node, - c: [ - { - t: leaf_node, - s: 'player_turn', - p: faction_id, - }, - ], - })); - game.engine.push({ - t: function_node, - f: 'resolve_fascist_test', - }); - game.engine.push({ - t: function_node, - f: 'setup_bag_of_glory', - }); + game.engine = player_order.map((faction_id) => + create_seq_node([create_leaf_node('player_turn', faction_id)]) + ); + game.engine.push(create_function_node('resolve_fascist_test')); + game.engine.push(create_function_node('setup_bag_of_glory')); next(); } const engine_functions: Record<string, Function> = { + check_activate_icon, end_of_turn, end_of_year, setup_bag_of_glory, @@ -360,8 +385,10 @@ function game_view(state: Game, player: Player) { fronts: game.fronts, hand: game.hands[faction_id], medaillons: game.medaillons, - selected_card: game.cards_in_play[faction_id], + selected_card: game.chosen_cards[faction_id], + tableaus: game.tableaus, tracks: game.tracks, + triggered_track_effects: game.triggered_track_effects, }; if (player !== game.active) { @@ -419,7 +446,7 @@ export function setup(seed: number, _scenario: string, _options: unknown) { [MODERATES_ID]: 0, pool: 14, }, - cards_in_play: { + chosen_cards: { [ANARCHISTS_ID]: null, [COMMUNISTS_ID]: null, [MODERATES_ID]: null, @@ -438,6 +465,11 @@ export function setup(seed: number, _scenario: string, _options: unknown) { [MODERATES_ID]: [], }, tracks: [5, 5, 6, 3, 3], + trash: { + [ANARCHISTS_ID]: [], + [COMMUNISTS_ID]: [], + [MODERATES_ID]: [], + }, triggered_track_effects: [[], [], [], [], []], log: [], undo: [], @@ -495,6 +527,13 @@ function start_turn() { // region STATES +states.activate_icon = { + inactive: 'activate an icon', + prompt() { + view.prompt = 'Choose an icon to activate' + } +} + states.add_glory = { inactive: 'add tokens to the Bag of Glory', prompt() { @@ -517,24 +556,43 @@ states.add_glory = { }, }; +states.add_to_front = { + inactive: 'add strength to a Front', + prompt() { + const args = get_active_node_args(); + const possible_fronts = get_fronts_to_add_to(args.t); + view.prompt = + possible_fronts.length === 1 + ? `Add strength to ${front_names[possible_fronts[0]]}` + : 'Add strength to a Front'; + for (let f of possible_fronts) { + gen_action_front(f); + } + }, + front(f: string) { + const value = get_active_node_args().v; + update_front(f, value); + resolve_active_and_proceed(); + }, +}; + states.attack_front = { inactive: 'attack a Front', prompt() { const node = get_active_node(); - const front = node.a.f; + const front = node.a.t; view.prompt = 'Attack ' + front_names[front]; if (front === 'd' || front === 'v') { const fronts = get_fronts_closest_to(front); fronts.forEach((id) => gen_action('front', id)); } else { - gen_action('front', front); + gen_action_front(front); } }, front(f: string) { const node = get_active_node(); const value = node.a.v; - game.fronts[f] += value; - log_h3(`${Math.abs(value)} attacks added to ${front_names[f]}`); + update_front(f, value); resolve_active_and_proceed(); }, }; @@ -543,9 +601,18 @@ states.choose_area_ap = { inactive: 'choose area to use Action Points', prompt() { view.prompt = 'Choose area of the board to affect'; - for (let track of tracks) { + for (const track of tracks) { gen_action_standee(track.id); } + const fronts = get_fronts_to_add_to(ANY); + for (const front of fronts) { + gen_action_front(front); + } + }, + front(f: string) { + const s = get_active_node_args().strength; + update_front(f, s); + resolve_active_and_proceed(); }, standee(track_id: number) { console.log('standee', track_id); @@ -562,22 +629,37 @@ states.choose_area_ap = { }, }; -states.move_track_up_or_down = { - inactive: 'move a track', +states.change_bonus = { + inactive: 'gain select Bonus', prompt() { - const node = get_active_node(); - view.prompt = `Move ${get_track_name(node.a.track_id)} up or down`; - gen_action('up'); - gen_action('down'); + const args = get_active_node_args(); + if ( + (args.v === ON && + game.bonuses[TEAMWORK_BONUS] === ON && + game.bonuses[MORALE_BONUS] === ON) || + (args.v === OFF && game.bonuses[args.t] === OFF) + ) { + gen_action('skip'); + } + if (args.t === ANY && args.v === ON) { + view.prompt = 'Turn on a Bonus'; + for (const bonus of bonuses) { + if (game.bonuses[bonus] === OFF) { + gen_action_bonus(bonus); + } + } + } else if (args.v === OFF) { + view.prompt = `Turn off ${bonus_names[args.t]}`; + gen_action_bonus(args.t); + } }, - down() { - const node = get_active_node(); - move_track(node.a.track_id, -1 * node.a.strength); + bonus(b: number) { + const value = get_active_node_args().v; + game.bonuses[b] = value; + log(`${bonus_names[b]} ${value === ON ? 'on' : 'off'}`); resolve_active_and_proceed(); }, - up() { - const node = get_active_node(); - move_track(node.a.track_id, node.a.strength); + skip() { resolve_active_and_proceed(); }, }; @@ -590,11 +672,24 @@ states.choose_card = { for (let c of hand) gen_action_card(c); }, card(c: CardId) { - log_h3(`${game.active} chooses a card`); - if (!game.cards_in_play) { - game.cards_in_play = {}; + game.chosen_cards[player_faction_map[game.active]] = c; + resolve_active_and_proceed(); + }, +}; + +states.gain_hero_points = { + inactive: 'gain Hero Points', + prompt() { + const value = get_active_node_args().v; + view.prompt = value > 1 ? `Gain ${value} Hero Points` : 'Gain 1 Hero Point'; + gen_action('gain_hp'); + }, + gain_hp() { + const value = get_active_node_args().v; + if (game.hero_points.pool > value) { + game.hero_points.pool -= value; + game.hero_points[get_active_faction()] += value; } - game.cards_in_play[player_faction_map[game.active]] = c; resolve_active_and_proceed(); }, }; @@ -602,7 +697,30 @@ states.choose_card = { states.lose_hero_points = { inactive: 'choose a Player', prompt() { - view.prompt = 'Lose Hero Points'; + const args = get_active_node_args(); + view.prompt = 'Choose player to lose Hero Points'; + if (args.t === PLAYER_WITH_MOST_HERO_POINTS) { + const factions = get_factions_with_most_hero_poins(); + console.log('faction', factions); + for (let faction_id of factions) { + gen_action(faction_player_map[faction_id]); + } + } + }, + Anarchist() { + const value = get_active_node_args().v; + lose_hero_point(ANARCHISTS_ID, value); + resolve_active_and_proceed(); + }, + Communist() { + const value = get_active_node_args().v; + lose_hero_point(ANARCHISTS_ID, value); + resolve_active_and_proceed(); + }, + Moderate() { + const value = get_active_node_args().v; + lose_hero_point(ANARCHISTS_ID, value); + resolve_active_and_proceed(); }, }; @@ -614,12 +732,32 @@ states.move_track = { const value = node.a.v; view.prompt = `Move ${tracks[track].name} ${value > 0 ? 'up' : 'down'}`; // return 'Decrease ' + tracks[effect.target].name; - gen_action_standee(track) + gen_action_standee(track); }, standee(s: number) { const node = get_active_node(); const value = node.a.v; - move_track(s, value) + move_track(s, value); + resolve_active_and_proceed(); + }, +}; + +states.move_track_up_or_down = { + inactive: 'move a track', + prompt() { + const node = get_active_node(); + view.prompt = `Move ${get_track_name(node.a.track_id)} up or down`; + gen_action('up'); + gen_action('down'); + }, + down() { + const node = get_active_node(); + move_track(node.a.track_id, -1 * node.a.strength); + resolve_active_and_proceed(); + }, + up() { + const node = get_active_node(); + move_track(node.a.track_id, node.a.strength); resolve_active_and_proceed(); }, }; @@ -628,21 +766,26 @@ states.player_turn = { inactive: 'play their turn', prompt() { const faction_id = get_faction_id(game.active); - const hero_points = game.hero_points[faction_id]; - view.prompt = - hero_points === 0 - ? 'Play your card' - : 'Play your card or spend Hero points'; + const can_spend_hp = game.hero_points[faction_id] > 0; + const can_play_card = game.hands[faction_id].includes( + game.chosen_cards[faction_id] + ); + view.prompt = 'Play a card or spend Hero points'; + if (!(can_play_card || can_spend_hp)) { + view.prompt = 'End turn'; + } else if (!can_play_card && can_spend_hp) { + view.prompt = 'Spend Hero Points or end turn'; + } // const card = game.cards_in_play[faction_id]; - if (game.cards_in_play[faction_id] !== null) { + if (can_play_card) { // gen_action_card(); gen_action('play_for_ap'); gen_action('play_for_event'); } else { gen_action('done'); } - if (hero_points > 0) { + if (can_spend_hp) { gen_action('spend_hp'); } }, @@ -651,34 +794,29 @@ states.player_turn = { }, play_for_ap() { const faction_id = get_faction_id(game.active); - const card = game.cards_in_play[faction_id]; + const card = game.chosen_cards[faction_id]; log_h3(`${game.active} plays ${cards[card].title} for the Action Points`); - if (!game.discard) { - game.discard = { - [ANARCHISTS_ID]: [], - [COMMUNISTS_ID]: [], - [MODERATES_ID]: [], - f: [], - }; - } array_remove(game.hands[faction_id], game.hands[faction_id].indexOf(card)); - game.discard[faction_id].push(card); - insert_before_active_node({ - t: leaf_node, - p: faction_id, - s: 'choose_area_ap', - a: { - strength: (cards[card] as PlayerCard).strength, - }, - }); + game.tableaus[faction_id].push(card); + insert_before_active_node( + create_seq_node([ + create_leaf_node('choose_area_ap', faction_id, { + strength: (cards[card] as PlayerCard).strength, + }), + create_function_node('check_activate_icon'), + ]) + ); next(); }, play_for_event() { const faction_id = get_faction_id(game.active); - const card = game.cards_in_play[faction_id]; + const card = game.chosen_cards[faction_id]; + array_remove(game.hands[faction_id], game.hands[faction_id].indexOf(card)); + game.trash[faction_id].push(card); log_h3(`${game.active} plays ${cards[card].title} for the Event`); - game.cards_in_play[faction_id] = null; - game.tableaus[faction_id].push(card); + + insert_after_active_node(create_effects_node(cards[card].effects)); + resolve_active_and_proceed(); }, spend_hp() { @@ -690,58 +828,58 @@ states.player_turn = { // insert spend hero points node before current node next(); }, - card(c: CardId) { - const faction = get_active_faction(); - log_h3(`${game.active} plays ${cards[c].title} to their tableau`); - if (!game.tableaus) { - game.tableaus = { - [ANARCHISTS_ID]: [], - [COMMUNISTS_ID]: [], - [MODERATES_ID]: [], - }; - } - game.cards_in_play[faction] = null; - game.tableaus[faction].push(c); - array_remove(game.hands[faction], game.hands[faction].indexOf(c)); - resolve_active_and_proceed(); - }, + // card(c: CardId) { + // const faction = get_active_faction(); + // log_h3(`${game.active} plays ${cards[c].title} to their tableau`); + // if (!game.tableaus) { + // game.tableaus = { + // [ANARCHISTS_ID]: [], + // [COMMUNISTS_ID]: [], + // [MODERATES_ID]: [], + // }; + // } + // game.chosen_cards[faction] = null; + // game.tableaus[faction].push(c); + // array_remove(game.hands[faction], game.hands[faction].indexOf(c)); + // resolve_active_and_proceed(); + // }, }; -states.resolve_event = { - inactive: 'resolve Fascist Event', - prompt() { - const card = get_current_event(); - const node = get_active_node(game.engine) as LeafNode; - const effect: Effect = card.effects[node.a]; - view.prompt = get_event_prompt(effect); - -if (effect.type === 'track') { - gen_action('standee', effect.target); - } else if ( - effect.type === 'hero_points' && - effect.target === PLAYER_WITH_MOST_HERO_POINTS - ) { - const factions = get_factions_with_most_hero_poins(); - for (let faction_id of factions) { - gen_action(get_player(faction_id)); - } - } - // for (let p = 0; p < 5; ++p) gen_action('standee', p); - }, +// states.resolve_event = { +// inactive: 'resolve Fascist Event', +// prompt() { +// const card = get_current_event(); +// const node = get_active_node(game.engine) as LeafNode; +// const effect: Effect = card.effects[node.a]; +// view.prompt = get_event_prompt(effect); + +// if (effect.type === 'track') { +// gen_action('standee', effect.target); +// } else if ( +// effect.type === 'hero_points' && +// effect.target === PLAYER_WITH_MOST_HERO_POINTS +// ) { +// const factions = get_factions_with_most_hero_poins(); +// for (let faction_id of factions) { +// gen_action(get_player(faction_id)); +// } +// } +// // for (let p = 0; p < 5; ++p) gen_action('standee', p); +// }, - Anarchist() { - lose_hero_point(ANARCHISTS_ID, 1); - resolve_active_and_proceed(); - }, - Communist() { - lose_hero_point(ANARCHISTS_ID, 1); - resolve_active_and_proceed(); - }, - Moderate() { - lose_hero_point(ANARCHISTS_ID, 1); - resolve_active_and_proceed(); - }, -}; +// Anarchist() { +// lose_hero_point(ANARCHISTS_ID, 1); +// resolve_active_and_proceed(); +// }, +// Communist() { +// lose_hero_point(ANARCHISTS_ID, 1); +// resolve_active_and_proceed(); +// }, +// Moderate() { +// lose_hero_point(ANARCHISTS_ID, 1); +// resolve_active_and_proceed(); +// }, +// }; states.spend_hero_points = { inactive: 'spend Hero points', @@ -793,6 +931,16 @@ function pop_undo() { // #region GAME FUNCTIONS +// Check if Morale bonus is on so player can activate icon +function check_activate_icon() { + if (game.bonuses[MORALE_BONUS] === ON) { + insert_after_active_node( + create_leaf_node('activate_icon', get_active_faction_id()) + ); + } + resolve_active_and_proceed(); +} + function end_of_turn() { // REMOVE playre tplems from the Fronts; log_h2('End of turn'); @@ -812,6 +960,18 @@ function resolve_fascist_test() { next(); } +// TODO: check for defeated / won fronts +function get_fronts_to_add_to(target: string): string[] { + console.log('get_fronts_to_add_to', target); + if (target === CLOSEST_TO_DEFEAT || target === CLOSEST_TO_VICTORY) { + return get_fronts_closest_to(target); + } else if (target === ANY) { + return ['a', 'm', 'n', 's']; + } else { + return [target]; + } +} + function move_track(track_id: number, change: number) { // Check if track can be moved // Updata values @@ -822,7 +982,7 @@ function move_track(track_id: number, change: number) { new_value = Math.max(new_value, track_id === GOVERNMENT ? 1 : 0); new_value = Math.min(new_value, 10); game.tracks[track_id] = new_value; - log(`${game.active} moves ${get_track_name(track_id)} to ${new_value}`); + log(`${get_track_name(track_id)} to ${new_value}`); const triggered_spaces = change > 0 ? make_list(current_value + 1, new_value).reverse() @@ -845,67 +1005,57 @@ function move_track(track_id: number, change: number) { }); } +// TODO: acccount for victory / defeat of front +function update_front(f: string, change: number) { + game.fronts[f] += change; + log(`${front_names[f]}: ${change > 0 ? '+' : ''}${change}`); +} + +function create_effects_node(effects: Effect[]): EngineNode { + const nodes = effects.reduce((accrued: EngineNode[], current: Effect) => { + const node = resolve_effect(current); + if (node !== null) { + accrued.push(node); + } + return accrued; + }, []); + return { + t: seq_node, + c: nodes, + }; +} + +const effect_type_state_map: Record<string, string> = { + attack: 'attack_front', + bonus: 'change_bonus', + front: 'add_to_front', + track: 'move_track', +}; + function resolve_effect( effect: Effect, faction: FactionId = get_active_faction() ): EngineNode | null { - if (effect.type === 'attack') { - return { - t: leaf_node, - p: faction, - s: 'attack_front', - a: { - f: effect.target, - v: effect.value, - }, - }; - } - if (effect.type === 'track') { - return { - t: leaf_node, - p: faction, - s: 'move_track', - a: { - t: effect.target, - v: effect.value, - }, - }; + const args = { + t: effect.target, + v: effect.value, + }; + let state = effect_type_state_map[effect.type]; + + if (state !== undefined) { + return create_leaf_node(state, faction, args); } + if ( effect.type === 'hero_points' && effect.target === PLAYER_WITH_MOST_HERO_POINTS ) { - return { - t: leaf_node, - p: faction, - s: 'lose_hero_points', - a: { - p: effect.target, - v: effect.value, - }, - }; + state = 'lose_hero_points'; } - return null; - - // if ( - // effect.type === 'attack' && - // (effect.target === 'd' || effect.target === 'v') - // ) { - // const fronts = get_fronts_closest_to(effect.target); - // fronts.forEach((id) => gen_action('front', id)); - // } else if (effect.type === 'attack') { - // gen_action('front', effect.target); - // } else if (effect.type === 'track') { - // gen_action('standee', effect.target); - // } else if ( - // effect.type === 'hero_points' && - // effect.target === PLAYER_WITH_MOST_HERO_POINTS - // ) { - // const factions = get_factions_with_most_hero_poins(); - // for (let faction_id of factions) { - // gen_action(get_player(faction_id)); - // } - // } + if (effect.type === 'hero_points' && effect.target === SELF) { + state = 'gain_hero_points'; + } + return state === undefined ? null : create_leaf_node(state, faction, args); } // #endregion @@ -936,13 +1086,16 @@ function draw_item(ids: number[]): number { } function lose_hero_point(faction: FactionId, value: number) { - const points_lost = Math.min(game.hero_points[faction], value); + const points_lost = Math.min(game.hero_points[faction], Math.abs(value)); game.hero_points.pool += points_lost; game.hero_points[faction] -= points_lost; + if (points_lost === 0) { + return; + } if (points_lost === 1) { - log(`${get_player(faction)} loses 1 Hero Point`); + log(`${get_player(faction)}: -1 Hero Point`); } else { - log(`${get_player(faction)} loses ${points_lost} Hero Points`); + log(`${get_player(faction)}: -${points_lost} Hero Points`); } } @@ -950,11 +1103,11 @@ function lose_hero_point(faction: FactionId, value: number) { // #region EVENTS -function get_current_event(): EventCard { - return cards[ - game.current_events[game.current_events.length - 1] - ] as EventCard; -} +// function get_current_event(): EventCard { +// return cards[ +// game.current_events[game.current_events.length - 1] +// ] as EventCard; +// } // function get_front_name(frontId: string) { // switch (frontId) { @@ -975,20 +1128,20 @@ function get_current_event(): EventCard { // } // } -function get_event_prompt(effect: Effect) { - let prompt = ''; - switch (effect.type) { - case 'attack': - return 'Attack ' + front_names[effect.target as string]; - case 'bonus': - break; - case 'hero_points': - return 'Select player with most Hero points'; - case 'track': - return 'Decrease ' + tracks[effect.target].name; - } - return prompt; -} +// function get_event_prompt(effect: Effect) { +// let prompt = ''; +// switch (effect.type) { +// case 'attack': +// return 'Attack ' + front_names[effect.target as string]; +// case 'bonus': +// break; +// case 'hero_points': +// return 'Select player with most Hero points'; +// case 'track': +// return 'Decrease ' + tracks[effect.target].name; +// } +// return prompt; +// } // function resolve_event_attack(target: string | number, value: number) { // switch (target) { @@ -1156,12 +1309,18 @@ function get_next_faction(faction_id: FactionId): FactionId { return role_ids[index + 1]; } -function get_factions_with_most_hero_poins() { - const most_hero_points = Math.max(...Object.values(game.hero_points)); - const faction_ids = []; - Object.entries(game.hero_points).forEach(([faction, hero_points]) => { - if (hero_points === most_hero_points) { - faction_ids.push(faction); +function get_factions_with_most_hero_poins(): FactionId[] { + let most_hero_points = null; + let faction_ids = []; + Object.entries(game.hero_points).forEach(([id, value]) => { + if (id === 'pool') { + return; + } + if (most_hero_points === null || value > most_hero_points) { + most_hero_points = value; + faction_ids = [id]; + } else if (most_hero_points === value) { + faction_ids.push(id); } }); return faction_ids; |