From f59d0e1b06873b3f0adb2aa4555eaede7258d7bf Mon Sep 17 00:00:00 2001 From: Daniel Schmidt Date: Wed, 10 Jan 2024 23:13:49 +0100 Subject: [PATCH] feat: artifact/card carousel, rework of game flow --- .gitignore | 3 +- assets/scripts/{cards => }/_debug.lua | 0 assets/scripts/_test.lua | 42 +++++ assets/scripts/_util.lua | 128 ++++++++++++++ assets/scripts/artifacts/bag_of_holding.lua | 14 -- assets/scripts/artifacts/deflector_shield.lua | 21 --- .../scripts/artifacts/gigantic_strength.lua | 26 --- assets/scripts/artifacts/gold_converter.lua | 22 --- assets/scripts/artifacts/holy_grail.lua | 14 -- assets/scripts/artifacts/juicy_fruit.lua | 12 -- assets/scripts/artifacts/radiant_seed.lua | 12 -- assets/scripts/artifacts/repulsion_stone.lua | 14 -- assets/scripts/artifacts/short_radiance.lua | 16 -- assets/scripts/artifacts/spiked_plant.lua | 14 -- assets/scripts/cards/berserker_rage.lua | 22 --- assets/scripts/cards/block.lua | 19 -- assets/scripts/cards/block_spikes.lua | 64 ------- assets/scripts/cards/combined_shot.lua | 19 -- assets/scripts/cards/fear.lua | 19 -- assets/scripts/cards/radiant_seed.lua | 23 --- assets/scripts/cards/rupture.lua | 19 -- assets/scripts/cards/shield_bash.lua | 47 ----- assets/scripts/definitions/api.lua | 40 ++++- assets/scripts/definitions/artifact.lua | 1 + assets/scripts/definitions/callbacks.lua | 1 + assets/scripts/definitions/event.lua | 2 +- assets/scripts/definitions/story_teller.lua | 8 +- assets/scripts/enemies/clean_bot.lua | 18 +- assets/scripts/enemies/rust_mite.lua | 36 ++-- assets/scripts/enemies/sand_stalker.lua | 29 ---- assets/scripts/enemies/shadow_assasin.lua | 70 -------- assets/scripts/equipment/_colors.lua | 3 + assets/scripts/equipment/_util.lua | 25 +++ assets/scripts/equipment/arm_mounted_gun.lua | 39 +++++ .../scripts/equipment/basic_hand_weapons.lua | 129 ++++++++++++++ .../{status_effects => equipment}/block.lua | 28 ++- assets/scripts/equipment/flash_bang.lua | 49 ++++++ assets/scripts/equipment/knock_out.lua | 34 ++++ .../{cards => equipment}/melee_hit.lua | 17 +- assets/scripts/events/act_0/enemies.lua | 33 ++++ .../events/{stage_0 => act_0}/start.lua | 47 +++-- .../scripts/events/misc/raising_the_bar.lua | 45 ----- assets/scripts/events/misc/recycle_device.lua | 53 ------ assets/scripts/events/misc/talking_being.lua | 59 ------- assets/scripts/events/stage_1/bio_kingdom.lua | 20 --- assets/scripts/events/stage_1/the_core.lua | 20 --- .../scripts/events/stage_1/the_wasteland.lua | 20 --- assets/scripts/status_effects/burn.lua | 18 -- assets/scripts/status_effects/fear.lua | 17 -- assets/scripts/status_effects/strength.lua | 20 --- assets/scripts/status_effects/vulnerable.lua | 20 --- assets/scripts/status_effects/weaken.lua | 20 --- assets/scripts/story_teller/act_0.lua | 26 +++ assets/scripts/story_teller/stage_0.lua | 38 ---- .../story_teller/stage_1_bio_kingdom.lua | 31 ---- .../scripts/story_teller/stage_1_the_core.lua | 29 ---- .../story_teller/stage_1_the_wasteland.lua | 29 ---- assets/scripts/story_teller/stage_2.lua | 19 -- assets/scripts/story_teller/stage_3.lua | 19 -- docs/LUA_API_DOCS.md | 76 +++++++- game/artifact.go | 1 + game/card.go | 1 + game/checkpoint.go | 36 +++- game/event.go | 1 + game/lua.go | 44 ++++- game/resources.go | 34 +++- game/session.go | 162 +++++++++++++++--- ui/components/artifact.go | 14 +- ui/components/card.go | 22 ++- ui/menus/carousel/carousel.go | 104 +++++++++++ ui/menus/eventview/eventview.go | 2 +- ui/menus/gameview/gameview.go | 60 ++++++- ui/menus/overview/overview.go | 20 ++- 73 files changed, 1142 insertions(+), 1117 deletions(-) rename assets/scripts/{cards => }/_debug.lua (100%) delete mode 100644 assets/scripts/artifacts/bag_of_holding.lua delete mode 100644 assets/scripts/artifacts/deflector_shield.lua delete mode 100644 assets/scripts/artifacts/gigantic_strength.lua delete mode 100644 assets/scripts/artifacts/gold_converter.lua delete mode 100644 assets/scripts/artifacts/holy_grail.lua delete mode 100644 assets/scripts/artifacts/juicy_fruit.lua delete mode 100644 assets/scripts/artifacts/radiant_seed.lua delete mode 100644 assets/scripts/artifacts/repulsion_stone.lua delete mode 100644 assets/scripts/artifacts/short_radiance.lua delete mode 100644 assets/scripts/artifacts/spiked_plant.lua delete mode 100644 assets/scripts/cards/berserker_rage.lua delete mode 100644 assets/scripts/cards/block.lua delete mode 100644 assets/scripts/cards/block_spikes.lua delete mode 100644 assets/scripts/cards/combined_shot.lua delete mode 100644 assets/scripts/cards/fear.lua delete mode 100644 assets/scripts/cards/radiant_seed.lua delete mode 100644 assets/scripts/cards/rupture.lua delete mode 100644 assets/scripts/cards/shield_bash.lua delete mode 100644 assets/scripts/enemies/sand_stalker.lua delete mode 100644 assets/scripts/enemies/shadow_assasin.lua create mode 100644 assets/scripts/equipment/_colors.lua create mode 100644 assets/scripts/equipment/_util.lua create mode 100644 assets/scripts/equipment/arm_mounted_gun.lua create mode 100644 assets/scripts/equipment/basic_hand_weapons.lua rename assets/scripts/{status_effects => equipment}/block.lua (65%) create mode 100644 assets/scripts/equipment/flash_bang.lua create mode 100644 assets/scripts/equipment/knock_out.lua rename assets/scripts/{cards => equipment}/melee_hit.lua (53%) create mode 100644 assets/scripts/events/act_0/enemies.lua rename assets/scripts/events/{stage_0 => act_0}/start.lua (65%) delete mode 100644 assets/scripts/events/misc/raising_the_bar.lua delete mode 100644 assets/scripts/events/misc/recycle_device.lua delete mode 100644 assets/scripts/events/misc/talking_being.lua delete mode 100644 assets/scripts/events/stage_1/bio_kingdom.lua delete mode 100644 assets/scripts/events/stage_1/the_core.lua delete mode 100644 assets/scripts/events/stage_1/the_wasteland.lua delete mode 100644 assets/scripts/status_effects/burn.lua delete mode 100644 assets/scripts/status_effects/fear.lua delete mode 100644 assets/scripts/status_effects/strength.lua delete mode 100644 assets/scripts/status_effects/vulnerable.lua delete mode 100644 assets/scripts/status_effects/weaken.lua create mode 100644 assets/scripts/story_teller/act_0.lua delete mode 100644 assets/scripts/story_teller/stage_0.lua delete mode 100644 assets/scripts/story_teller/stage_1_bio_kingdom.lua delete mode 100644 assets/scripts/story_teller/stage_1_the_core.lua delete mode 100644 assets/scripts/story_teller/stage_1_the_wasteland.lua delete mode 100644 assets/scripts/story_teller/stage_2.lua delete mode 100644 assets/scripts/story_teller/stage_3.lua create mode 100644 ui/menus/carousel/carousel.go diff --git a/.gitignore b/.gitignore index cfa5865..e45dfd8 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,5 @@ node_modules/ cpu* heap* profile* -file_index.json \ No newline at end of file +file_index.json +__old/ \ No newline at end of file diff --git a/assets/scripts/cards/_debug.lua b/assets/scripts/_debug.lua similarity index 100% rename from assets/scripts/cards/_debug.lua rename to assets/scripts/_debug.lua diff --git a/assets/scripts/_test.lua b/assets/scripts/_test.lua index 0b7676a..6b97839 100644 --- a/assets/scripts/_test.lua +++ b/assets/scripts/_test.lua @@ -11,6 +11,48 @@ function assert_chain(tests) return nil end +---assert_card_present asserts that the player's first card is of a certain type, returning an error message if not +---@param id type_id +---@return string|nil +function assert_card_present(id) + local cards = get_cards(PLAYER_ID) + + if not cards[1] then + return "Card not in hand" + end + + local card = get_card_instance(cards[1]) + if card.type_id ~= id then + return "Card has wrong type: " .. card.type_id + end + + return nil +end + +---assert_cast_damage asserts that the player's first card deals a certain amount of damage, returning an error message if not +---@param id type_id +---@param dmg number +---@return string|nil +function assert_cast_damage(id, dmg) + local dummy = add_actor_by_enemy("DUMMY") + local cards = get_cards(PLAYER_ID) + + if not cards[1] then + return "Card not in hand" + end + + local card = get_card_instance(cards[1]) + if card.type_id ~= id then + return "Card has wrong type: " .. card.type_id + end + + cast_card(cards[1], dummy) + + if get_actor(dummy).hp ~= 100 - dmg then + return "Expected " .. tostring(100 - dmg) .. " health, got " .. get_actor(dummy).hp + end +end + ---assert_status_effect_count asserts that the player has a certain number of status effects, returning an error message if not ---@param count number ---@return string|nil diff --git a/assets/scripts/_util.lua b/assets/scripts/_util.lua index fade4a8..9607ef7 100644 --- a/assets/scripts/_util.lua +++ b/assets/scripts/_util.lua @@ -3,3 +3,131 @@ function highlight(val) return text_underline(text_bold("[" .. tostring(val) .. "]")) end + +---highlight_warn some value with warning colors +---@param val any +function highlight_warn(val) + return text_underline(text_bold(text_red("[" .. tostring(val) .. "]"))) +end + +---choose_weighted chooses an item from a list of choices, with a weight for each item. +---@param choices table +---@param weights number[] +---@return string +function choose_weighted(choices, weights) + print(choices, weights) + + local total_weight = 0 + for _, weight in ipairs(weights) do + total_weight = total_weight + weight + end + + local random = math.random() * total_weight + for i, weight in ipairs(weights) do + random = random - weight + if random <= 0 then + return choices[i] + end + end + + return choices[#choices] +end + +---table.contains check if a table contains an element. +function table.contains(table, element) + for _, value in pairs(table) do + if value == element then + return true + end + end + return false +end + +---find_by_tags find all items with the given tags. +---@param items artifact|card +---@param tags string[] +function find_by_tags(items, tags) + local found = {} + for _, item in pairs(items) do + for _, tag in pairs(tags) do + if item.tags == nil then + goto continue + end + if not table.contains(item.tags, tag) then + goto continue + end + end + + table.insert(found, item) + + ::continue:: + end + return found +end + +---find_artifacts_by_tags find all artifacts with the given tags. +---@param tags string[] +---@return artifact[] +function find_artifacts_by_tags(tags) + return find_by_tags(registered.artifact, tags) +end + +---find_cards_by_tags find all cards with the given tags. +---@param tags string[] +---@return card[] +function find_cards_by_tags(tags) + return find_by_tags(registered.card, tags) +end + +---find_events_by_tags find all events with the given tags. +---@param tags string[] +---@return event[] +function find_events_by_tags(tags) + return find_by_tags(registered.event, tags) +end + +---choose_weighted_by_price choose a random item from the given list, weighted by price. +---@param items artifact|card +---@return string +function choose_weighted_by_price(items) + return choose_weighted( + fun.iter(items):map(function(item) return item.id or item.type_id end):totable(), + fun.iter(items):map(function(item) return item.price end):totable() + ) +end + +---clear_cards_by_tag remove all cards with tag. +---@param tag string tag to remove +---@param excluded? table optional table of guids to exclude. +function clear_cards_by_tag(tag, excluded) + for _, guid in pairs(get_cards(PLAYER_ID)) do + if excluded and table.contains(excluded, guid) then + goto continue + end + + local tags = get_card(guid).tags + if table.contains(tags, tag) then + remove_card(guid) + end + + ::continue:: + end +end + +---clear_artifacts_by_tag remove all artifacts with tag. +---@param tag string tag to remove +---@param excluded table optional table of guids to exclude. +function clear_artifacts_by_tag(tag, excluded) + for _, guid in pairs(get_artifacts(PLAYER_ID)) do + if excluded and table.contains(excluded, guid) then + goto continue + end + + local tags = get_artifact(guid).tags + if table.contains(tags, tag) then + remove_artifact(guid) + end + + ::continue:: + end +end diff --git a/assets/scripts/artifacts/bag_of_holding.lua b/assets/scripts/artifacts/bag_of_holding.lua deleted file mode 100644 index 120955c..0000000 --- a/assets/scripts/artifacts/bag_of_holding.lua +++ /dev/null @@ -1,14 +0,0 @@ -register_artifact("BAG_OF_HOLDING", { - name = "Bag of Holding", - description = "Start with a additional card at the beginning of combat.", - price = 50, - order = 0, - callbacks = { - on_player_turn = function(ctx) - if ctx.owner == PLAYER_ID and ctx.round == 0 then - player_draw_card(1) - end - return nil - end - } -}); diff --git a/assets/scripts/artifacts/deflector_shield.lua b/assets/scripts/artifacts/deflector_shield.lua deleted file mode 100644 index 6403716..0000000 --- a/assets/scripts/artifacts/deflector_shield.lua +++ /dev/null @@ -1,21 +0,0 @@ -register_artifact("DEFLECTOR_SHIELD", { - name = "Deflector Shield", - description = "Gain 8 block at the start of combat.", - price = 50, - order = 0, - callbacks = { - on_player_turn = function(ctx) - if ctx.round == 0 then - give_status_effect("BLOCK", ctx.owner, 8) - end - return nil - end - }, - test = function() - add_actor_by_enemy("DUMMY") - return assert_chain({ - function () return assert_status_effect_count(1) end, - function () return assert_status_effect("BLOCK", 8) end - }) - end -}); diff --git a/assets/scripts/artifacts/gigantic_strength.lua b/assets/scripts/artifacts/gigantic_strength.lua deleted file mode 100644 index fd580f8..0000000 --- a/assets/scripts/artifacts/gigantic_strength.lua +++ /dev/null @@ -1,26 +0,0 @@ -register_artifact("GIGANTIC_STRENGTH", { - name = "Stone Of Gigantic Strength", - description = "Double all damage dealt.", - price = 250, - order = 0, - callbacks = { - on_damage_calc = function(ctx) - if ctx.source == ctx.owner then - return ctx.damage * 2 - end - return nil - end - }, - test = function() - local dummy = add_actor_by_enemy("DUMMY") - - local hp_before = get_actor(dummy).hp - deal_damage(PLAYER_ID, dummy, 1) - local hp_after = get_actor(dummy).hp - - if hp_after == hp_before - 2 then - return nil - end - return "Damage was not doubled. Before:" .. hp_before .. " After:" .. hp_after - end -}) diff --git a/assets/scripts/artifacts/gold_converter.lua b/assets/scripts/artifacts/gold_converter.lua deleted file mode 100644 index 61acedd..0000000 --- a/assets/scripts/artifacts/gold_converter.lua +++ /dev/null @@ -1,22 +0,0 @@ -register_artifact("GOLD_CONVERTER", { - name = "Gold Converter", - description = "Gain 10 extra gold for each killed enemy.", - price = 50, - order = 0, - callbacks = { - on_actor_die = function(ctx) - if ctx.owner == PLAYER_ID and ctx.owner == ctx.source then - give_player_gold(10) - end - return nil - end - }, - test = function() - local dummy = add_actor_by_enemy("DUMMY") - deal_damage(PLAYER_ID, dummy, 10000) - if get_player().gold == 10 then - return nil - end - return "Expected 10 gold, got " .. get_player().gold - end -}); diff --git a/assets/scripts/artifacts/holy_grail.lua b/assets/scripts/artifacts/holy_grail.lua deleted file mode 100644 index d8fcb5e..0000000 --- a/assets/scripts/artifacts/holy_grail.lua +++ /dev/null @@ -1,14 +0,0 @@ -register_artifact("HOLY_GRAIL", { - name = "Holy Grail", - description = "At the start of each turn, heal for 2 HP for each card in your hand.", - price = 150, - order = 100, -- Evaluate late so that other draw artifacts have priority. - callbacks = { - on_player_turn = function(ctx) - local num_cards = #get_cards(ctx.owner) - local heal_amount = num_cards * 2 - heal(ctx.owner, ctx.owner, heal_amount) - return nil - end - } -}); diff --git a/assets/scripts/artifacts/juicy_fruit.lua b/assets/scripts/artifacts/juicy_fruit.lua deleted file mode 100644 index f6e5815..0000000 --- a/assets/scripts/artifacts/juicy_fruit.lua +++ /dev/null @@ -1,12 +0,0 @@ -register_artifact("JUICY_FRUIT", { - name = "Juicy Fruit", - description = "Tastes good and boosts your HP.", - price = 80, - order = 0, - callbacks = { - on_pick_up = function(ctx) - actor_add_max_hp(ctx.owner, 10) - return nil - end - } -}); diff --git a/assets/scripts/artifacts/radiant_seed.lua b/assets/scripts/artifacts/radiant_seed.lua deleted file mode 100644 index 9ffe4d5..0000000 --- a/assets/scripts/artifacts/radiant_seed.lua +++ /dev/null @@ -1,12 +0,0 @@ -register_artifact("RADIANT_SEED", { - name = "Radiant Seed", - description = "A small glowing seed.", - price = 140, - order = 0, - callbacks = { - on_pick_up = function(ctx) - give_card("RADIANT_SEED", ctx.owner) - return nil - end - } -}); diff --git a/assets/scripts/artifacts/repulsion_stone.lua b/assets/scripts/artifacts/repulsion_stone.lua deleted file mode 100644 index 22e046f..0000000 --- a/assets/scripts/artifacts/repulsion_stone.lua +++ /dev/null @@ -1,14 +0,0 @@ -register_artifact("REPULSION_STONE", { - name = "Repulsion Stone", - description = "For each damage taken heal for 2", - price = 100, - order = 0, - callbacks = { - on_damage = function(ctx) - if ctx.target == ctx.owner then - heal(ctx.owner, ctx.owner, 2) - end - return nil - end - } -}); diff --git a/assets/scripts/artifacts/short_radiance.lua b/assets/scripts/artifacts/short_radiance.lua deleted file mode 100644 index cccbf93..0000000 --- a/assets/scripts/artifacts/short_radiance.lua +++ /dev/null @@ -1,16 +0,0 @@ -register_artifact("SHORT_RADIANCE", { - name = "Short Radiance", - description = "Apply 1 vulnerable at the start of combat.", - price = 50, - order = 0, - callbacks = { - on_player_turn = function(ctx) - if ctx.round == 0 then - each(function(val) - give_status_effect("VULNERABLE", val) - end, pairs(get_opponent_guids(ctx.owner))) - end - return nil - end - } -}); diff --git a/assets/scripts/artifacts/spiked_plant.lua b/assets/scripts/artifacts/spiked_plant.lua deleted file mode 100644 index 0c0fc6c..0000000 --- a/assets/scripts/artifacts/spiked_plant.lua +++ /dev/null @@ -1,14 +0,0 @@ -register_artifact("SPIKED_PLANT", { - name = "Spiked Plant", - description = "Deal 2 damage back to enemy attacks.", - price = 50, - order = 0, - callbacks = { - on_damage = function(ctx) - if ctx.source ~= ctx.owner and ctx.owner == ctx.target then - deal_damage(ctx.owner, ctx.source, 2) - end - return nil - end - } -}); diff --git a/assets/scripts/cards/berserker_rage.lua b/assets/scripts/cards/berserker_rage.lua deleted file mode 100644 index ef0ebf9..0000000 --- a/assets/scripts/cards/berserker_rage.lua +++ /dev/null @@ -1,22 +0,0 @@ -register_card("BERSERKER_RAGE", { - name = "Berserker Rage", - description = "Gain " .. highlight("3 action points") .. ", but take 30% (-10% per level) of your HP as damage.", - state = function(ctx) - return "Gain " .. - highlight("3 action points") .. ", but take " .. highlight(tostring(30 - ctx.level * 10) .. "%") .. " (" .. - tostring(get_player().hp * (0.3 - ctx.level * 0.1)) .. ") of your HP as damage." - end, - tags = { "BUFF" }, - max_level = 0, - color = "#d8a448", - need_target = false, - point_cost = 0, - price = 100, - callbacks = { - on_cast = function(ctx) - player_give_action_points(3) - deal_damage(ctx.caster, ctx.caster, get_player().hp * (0.3 - ctx.level * 0.1), true) - return nil - end - } -}) diff --git a/assets/scripts/cards/block.lua b/assets/scripts/cards/block.lua deleted file mode 100644 index ad274be..0000000 --- a/assets/scripts/cards/block.lua +++ /dev/null @@ -1,19 +0,0 @@ -register_card("BLOCK", { - name = "Block", - description = "Shield yourself and gain 5 " .. highlight("block") .. ".", - state = function(ctx) - return "Shield yourself and gain " .. highlight(5 + ctx.level * 3) .. " block." - end, - tags = { "DEF" }, - max_level = 1, - color = "#219ebc", - need_target = false, - point_cost = 1, - price = 40, - callbacks = { - on_cast = function(ctx) - give_status_effect("BLOCK", ctx.caster, 5 + ctx.level * 3) - return nil - end - } -}) diff --git a/assets/scripts/cards/block_spikes.lua b/assets/scripts/cards/block_spikes.lua deleted file mode 100644 index e1a5318..0000000 --- a/assets/scripts/cards/block_spikes.lua +++ /dev/null @@ -1,64 +0,0 @@ -register_card("BLOCK_SPIKES", { - name = "Block Spikes", - description = "Transforms " .. highlight("block") .. " to damage.", - state = function(ctx) - -- Fetch all BLOCK instances of owner - local blocks = fun.iter(pairs(get_actor_status_effects(ctx.owner))):map(get_status_effect_instance):filter(function(val) - return val.type_id == "BLOCK" - end):totable() - - -- Sum stacks to get damage - local damage = fun.iter(pairs(blocks)):reduce(function(acc, val) - return acc + val.stacks - end, 0) - - return "Transforms block to " .. highlight(damage) .. " damage." - end, - max_level = 0, - color = "#895cd6", - need_target = true, - point_cost = 1, - price = 100, - callbacks = { - on_cast = function(ctx) - -- Fetch all BLOCK instances of caster - local blocks = fun.iter(pairs(get_actor_status_effects(ctx.caster))):map(get_status_effect_instance):filter(function(val) - return val.type_id == "BLOCK" - end):totable() - - -- Sum stacks to get damage - local damage = fun.iter(pairs(blocks)):reduce(function(acc, val) - return acc + val.stacks - end, 0) - - if damage == 0 then - return "No block status effect present!" - end - - -- Remove BLOCKs - fun.iter(pairs(blocks)):for_each(function(val) - remove_status_effect(val.guid) - end) - - -- Deal Damage - deal_damage(ctx.caster, ctx.target, damage) - - return nil - end - }, - test = function () - give_status_effect("BLOCK", PLAYER_ID, 10) - return assert_chain({ - function () assert_status_effect_count(1) end, - function () assert_status_effect("BLOCK", 10) end, - function () - local dummy = add_actor_by_enemy("DUMMY") - local cards = get_cards(PLAYER_ID) - cast_card(cards[1], dummy) - if get_actor(dummy).hp ~= 90 then - return "Expected 90 health, got " .. get_actor(dummy).hp - end - end - }) - end -}) diff --git a/assets/scripts/cards/combined_shot.lua b/assets/scripts/cards/combined_shot.lua deleted file mode 100644 index 62a489c..0000000 --- a/assets/scripts/cards/combined_shot.lua +++ /dev/null @@ -1,19 +0,0 @@ -register_card("COMBINED_SHOT", { - name = "Combined Shot", - description = "Deal " .. highlight(5) .. " (+5 for each level) damage for each enemy.", - state = function(ctx) - return "Deal " .. highlight((5 + ctx.level * 5) * #get_opponent_guids(ctx.owner)) .. " damage for each enemy." - end, - tags = { "ATK" }, - max_level = 1, - color = "#d8a448", - need_target = true, - point_cost = 1, - price = 150, - callbacks = { - on_cast = function(ctx) - deal_damage(ctx.caster, ctx.target, (5 + ctx.level * 5) * #get_opponent_guids(ctx.owner)) - return nil - end - } -}) diff --git a/assets/scripts/cards/fear.lua b/assets/scripts/cards/fear.lua deleted file mode 100644 index 32f3cd6..0000000 --- a/assets/scripts/cards/fear.lua +++ /dev/null @@ -1,19 +0,0 @@ -register_card("FEAR", { - name = "Fear", - description = "Inflict " .. highlight("fear") .. " on the target, causing them to miss their next turn.", - state = function(ctx) - return nil - end, - tags = { "CC" }, - max_level = 0, - color = "#725e9c", - need_target = true, - point_cost = 2, - price = 80, - callbacks = { - on_cast = function(ctx) - give_status_effect("FEAR", ctx.target) - return nil - end - } -}) diff --git a/assets/scripts/cards/radiant_seed.lua b/assets/scripts/cards/radiant_seed.lua deleted file mode 100644 index c275649..0000000 --- a/assets/scripts/cards/radiant_seed.lua +++ /dev/null @@ -1,23 +0,0 @@ -register_card("RADIANT_SEED", { - name = "Radiant Seed", - description = "Inflict 10 (+2 for each upgrade) damage to all enemies, but also causes 5 (-2 for each upgrade) damage to the caster.", - state = function(ctx) - return "Inflict " .. highlight(10 + ctx.level * 2) .. " damage to all enemies, but also causes " .. highlight(5 - ctx.level * 2) .. - " damage to the caster." - end, - tags = { "ATK" }, - max_level = 1, - color = "#82c93e", - need_target = false, - point_cost = 2, - price = 120, - callbacks = { - on_cast = function(ctx) - -- Deal damage to caster without any modifiers applying - deal_damage(ctx.caster, ctx.caster, 5 - ctx.level * 2, true) - -- Deal damage to opponents - deal_damage_multi(ctx.caster, get_opponent_guids(ctx.caster), 10 + ctx.level * 2) - return nil - end - } -}) diff --git a/assets/scripts/cards/rupture.lua b/assets/scripts/cards/rupture.lua deleted file mode 100644 index cf47df2..0000000 --- a/assets/scripts/cards/rupture.lua +++ /dev/null @@ -1,19 +0,0 @@ -register_card("RUPTURE", { - name = "Rupture", - description = "Inflict your enemy with " .. highlight("Vulnerable") .. ".", - state = function(ctx) - return "Inflict your enemy with " .. highlight(tostring(1 + ctx.level) .. " Vulnerable") .. "." - end, - tags = { "ATK" }, - max_level = 3, - color = "#cf532d", - need_target = true, - point_cost = 1, - price = 30, - callbacks = { - on_cast = function(ctx) - give_status_effect("VULNERABLE", ctx.target, 1 + ctx.level) - return nil - end - } -}) diff --git a/assets/scripts/cards/shield_bash.lua b/assets/scripts/cards/shield_bash.lua deleted file mode 100644 index 8763edb..0000000 --- a/assets/scripts/cards/shield_bash.lua +++ /dev/null @@ -1,47 +0,0 @@ -register_card("SHIELD_BASH", { - name = "Shield Bash", - description = "Deal 4 (+2 for each upgrade) damage to the enemy and gain " .. highlight("block") .. - " status effect equal to the damage dealt.", - state = function(ctx) - return "Deal " .. highlight(4 + ctx.level * 2) .. " damage to the enemy and gain " .. highlight("block") .. - " status effect equal to the damage dealt." - end, - tags = { "ATK" }, - max_level = 1, - color = "#ff5722", - need_target = true, - point_cost = 1, - price = 40, - callbacks = { - on_cast = function(ctx) - local damage = deal_damage(ctx.caster, ctx.target, 4 + ctx.level * 2) - give_status_effect("BLOCK", ctx.caster, damage) - return nil - end - }, - test = function() - local dummy = add_actor_by_enemy("DUMMY") - local cards = get_cards(PLAYER_ID) - - -- Check if the card is in the player's hand - if not cards[1] then - return "Card not in hand" - end - - local card = get_card_instance(cards[1]) - if card.type_id ~= "SHIELD_BASH" then - return "Card has wrong type: " .. card.type_id - end - - cast_card(cards[1], dummy) - - if get_actor(dummy).hp ~= 96 then - return "Expected 96 health, got " .. get_actor(dummy).hp - end - - return assert_chain({ - function () assert_status_effect_count(1) end, - function () assert_status_effect("BLOCK", 4) end - }) - end -}) diff --git a/assets/scripts/definitions/api.lua b/assets/scripts/definitions/api.lua index 2b44bef..8d84725 100644 --- a/assets/scripts/definitions/api.lua +++ b/assets/scripts/definitions/api.lua @@ -61,16 +61,15 @@ function text_bg(color, value) end ---@return string function text_bold(value) end ---- Makes the text foreground colored. Takes hex values like #ff0000. ----@param color string +--- Makes the text italic. ---@param value any ---@return string -function text_color(color, value) end +function text_italic(value) end ---- Makes the text italic. +--- Makes the text colored red. ---@param value any ---@return string -function text_italic(value) end +function text_red(value) end --- Makes the text underlined. ---@param value any @@ -125,10 +124,14 @@ function get_event_history() end ---@return fight_state function get_fight() end ---- Gets the number of stages cleared. +--- Gets the fight round. ---@return number function get_fight_round() end +--- Gets the number of stages cleared. +---@return number +function get_stages_cleared() end + --- Checks if the event happened at least once. ---@param event_id type_id ---@return boolean @@ -170,6 +173,16 @@ function actor_add_hp(guid, amount) end ---@param amount number function actor_add_max_hp(guid, amount) end +--- Sets the hp value of a actor to a number. This won't trigger any on_damage callbacks +---@param guid guid +---@param amount number +function actor_set_hp(guid, amount) end + +--- Sets the max hp value of a actor to a number. +---@param guid guid +---@param amount number +function actor_set_max_hp(guid, amount) end + --- Creates a new enemy fighting against the player. Example ``add_actor_by_enemy("RUST_MITE")``. ---@param enemy_guid type_id ---@return string @@ -218,6 +231,11 @@ function get_artifact(id) end ---@return artifact_instance function get_artifact_instance(guid) end +--- Returns all the artifacts guids from the given actor. +---@param actor_guid string +---@return guid[] +function get_artifacts(actor_guid) end + --- Gives a actor a artifact. Returns the guid of the newly created artifact. ---@param type_id type_id ---@param actor guid @@ -316,7 +334,7 @@ function upgrade_random_card(actor_guid) end -- Damage & Heal -- ##################################### ---- Deal damage to a enemy from one source. If flat is true the damage can't be modified by status effects or artifacts. Returns the damage that was dealt. +--- Deal damage from one source to a target. If flat is true the damage can't be modified by status effects or artifacts. Returns the damage that was dealt. ---@param source guid ---@param target guid ---@param damage number @@ -338,6 +356,14 @@ function deal_damage_multi(source, targets, damage, flat) end ---@param amount number function heal(source, target, amount) end +--- Simulate damage from a source to a target. If flat is true the damage can't be modified by status effects or artifacts. Returns the damage that would be dealt. +---@param source guid +---@param target guid +---@param damage number +---@param flat? boolean +---@return number +function simulate_deal_damage(source, target, damage, flat) end + -- ##################################### -- Player Operations -- ##################################### diff --git a/assets/scripts/definitions/artifact.lua b/assets/scripts/definitions/artifact.lua index 64f1841..75c91f8 100644 --- a/assets/scripts/definitions/artifact.lua +++ b/assets/scripts/definitions/artifact.lua @@ -4,6 +4,7 @@ ---@field id? type_id ---@field name? string ---@field description? string +---@field tags? string[] ---@field price? number ---@field order? number ---@field callbacks? callbacks diff --git a/assets/scripts/definitions/callbacks.lua b/assets/scripts/definitions/callbacks.lua index 92f49a5..fb57c76 100644 --- a/assets/scripts/definitions/callbacks.lua +++ b/assets/scripts/definitions/callbacks.lua @@ -11,6 +11,7 @@ ---@field level? number ---@field tags? string[] ---@field damage? number +---@field simulated? boolean ---@field heal? number ---@field stacks? number ---@field round? number diff --git a/assets/scripts/definitions/event.lua b/assets/scripts/definitions/event.lua index 0565057..17fd8bc 100644 --- a/assets/scripts/definitions/event.lua +++ b/assets/scripts/definitions/event.lua @@ -20,6 +20,6 @@ ---@field description string ---@field choices event_choice[] ---@field on_enter? fun(ctx:event_on_enter_ctx):nil ----@field on_end fun(ctx:event_choice_ctx):next_game_state|nil +---@field on_end? fun(ctx:event_choice_ctx):next_game_state|nil ---@field test? fun():nil|string ---@field base_game? boolean \ No newline at end of file diff --git a/assets/scripts/definitions/story_teller.lua b/assets/scripts/definitions/story_teller.lua index 64bb71d..0198e03 100644 --- a/assets/scripts/definitions/story_teller.lua +++ b/assets/scripts/definitions/story_teller.lua @@ -1,7 +1,7 @@ ---@meta ---@class story_teller ----@field id type_id ----@field active fun():next_game_state ----@field decide fun():number ----@field base_game boolean \ No newline at end of file +---@field id? type_id +---@field active fun():number +---@field decide fun():next_game_state +---@field base_game? boolean \ No newline at end of file diff --git a/assets/scripts/enemies/clean_bot.lua b/assets/scripts/enemies/clean_bot.lua index 668e7e1..7b0b61c 100644 --- a/assets/scripts/enemies/clean_bot.lua +++ b/assets/scripts/enemies/clean_bot.lua @@ -5,30 +5,30 @@ register_enemy("CLEAN_BOT", { (* *) )#(]], color = "#32a891", - initial_hp = 25, - max_hp = 25, + initial_hp = 13, + max_hp = 13, gold = 15, intend = function(ctx) local self = get_actor(ctx.guid) - if self.hp <= 8 then - return "Block " .. highlight(4) + if self.hp <= 4 then + return "Block " .. highlight(2) end - return "Deal " .. highlight(7) .. " damage" + return "Deal " .. highlight(2) .. " damage" end, callbacks = { on_player_turn = function(ctx) local self = get_actor(ctx.guid) - if self.hp <= 8 then - give_status_effect("BLOCK", ctx.guid, 4) + if self.hp <= 4 then + give_status_effect("BLOCK", ctx.guid, 2) end end, on_turn = function(ctx) local self = get_actor(ctx.guid) - if self.hp > 8 then - deal_damage(ctx.guid, PLAYER_ID, 7) + if self.hp > 4 then + deal_damage(ctx.guid, PLAYER_ID, 2) end return nil diff --git a/assets/scripts/enemies/rust_mite.lua b/assets/scripts/enemies/rust_mite.lua index 298876f..0adc1ab 100644 --- a/assets/scripts/enemies/rust_mite.lua +++ b/assets/scripts/enemies/rust_mite.lua @@ -1,24 +1,24 @@ register_enemy("RUST_MITE", { - name = "Rust Mite", - description = "Loves to eat metal.", + name = l("enemies.RUST_MITE.name", "Rust Mite"), + description = l("enemies.RUST_MITE.description", "A small robot that eats metal."), look = "/v\\", color = "#e6e65a", - initial_hp = 22, - max_hp = 22, + initial_hp = 12, + max_hp = 12, gold = 10, intend = function(ctx) if ctx.round % 4 == 0 then - return "Gather strength" + return "Load battery" end - return "Deal " .. highlight(6) .. " damage" + return "Deal " .. highlight(simulate_deal_damage(ctx.guid, PLAYER_ID, 1)) .. " damage" end, callbacks = { on_turn = function(ctx) if ctx.round % 4 == 0 then - give_status_effect("RITUAL", ctx.guid) + give_status_effect("CHARGED", ctx.guid) else - deal_damage(ctx.guid, PLAYER_ID, 6) + deal_damage(ctx.guid, PLAYER_ID, 1) end return nil @@ -26,21 +26,23 @@ register_enemy("RUST_MITE", { } }) -register_status_effect("RITUAL", { - name = "Ritual", - description = "Gain strength each round", - look = "Rit", - foreground = "#bb3e03", +register_status_effect("CHARGED", { + name = l("status_effects.CHARGED.name", "Charged"), + description = l("status_effects.CHARGED.description", "Attacks will deal more damage per stack."), + look = "CHRG", + foreground = "#207BE7", state = function(ctx) - return nil + return string.format(l("status_effects.CHARGED.state", "Attacks deal %s more damage"), highlight(ctx.stacks * 1)) end, can_stack = true, decay = DECAY_NONE, rounds = 0, callbacks = { - on_player_turn = function(ctx) - local guid = give_status_effect("STRENGTH", ctx.owner) - set_status_effect_stacks(guid, 3 + ctx.stacks) + on_damage_calc = function(ctx) + if ctx.source == ctx.owner then + return ctx.damage + 1 * ctx.stacks + end + return nil end } }) diff --git a/assets/scripts/enemies/sand_stalker.lua b/assets/scripts/enemies/sand_stalker.lua deleted file mode 100644 index d11709d..0000000 --- a/assets/scripts/enemies/sand_stalker.lua +++ /dev/null @@ -1,29 +0,0 @@ -register_enemy("SAND_STALKER", { - name = "Sand Stalker", - description = "It waits for its prey to come closer.", - look = "( ° ° )", - color = "#8e4028", - initial_hp = 25, - max_hp = 25, - gold = 20, - intend = function(ctx) - if ctx.round % 4 == 0 then - return "Weaken your resolve" - end - - return "Deal " .. highlight(7) .. " damage" - end, - callbacks = { - on_turn = function(ctx) - if ctx.round % 4 == 0 then - if deal_damage(ctx.guid, PLAYER_ID, 5) > 0 then - give_status_effect("WEAKEN", PLAYER_ID, 1) - end - else - deal_damage(ctx.guid, PLAYER_ID, 7) - end - - return nil - end - } -}) diff --git a/assets/scripts/enemies/shadow_assasin.lua b/assets/scripts/enemies/shadow_assasin.lua deleted file mode 100644 index c09b93e..0000000 --- a/assets/scripts/enemies/shadow_assasin.lua +++ /dev/null @@ -1,70 +0,0 @@ -register_enemy("SHADOW_ASSASSIN", { - name = "Shadow Assassin", - description = "A master of stealth and deception.", - look = "???", - color = "#6c5b7b", - initial_hp = 20, - max_hp = 20, - gold = 30, - intend = function(ctx) - local bleeds = fun.iter(pairs(get_actor_status_effects(PLAYER_ID))) - :map(get_status_effect_instance) - :filter(function(val) - return val.type_id == "BLEED" - end):totable() - - if #bleeds > 0 then - return "Deal " .. highlight(10) .. " damage" - elseif ctx.round % 3 == 0 then - return "Inflict bleed" - else - return "Deal " .. highlight(5) .. " damage" - end - - return nil - end, - callbacks = { - on_turn = function(ctx) - -- Count bleed stacks - local bleeds = fun.iter(pairs(get_actor_status_effects(PLAYER_ID))) - :map(get_status_effect_instance) - :filter(function( - val) - return val.type_id == "BLEED" - end):totable() - - if #bleeds > 0 then - -- If bleeding do more damage - deal_damage(ctx.guid, PLAYER_ID, 10) - elseif ctx.round % 3 == 0 then - -- Try to bleed every 2 rounds with 3 dmg - if deal_damage(ctx.guid, PLAYER_ID, 3) > 0 then - give_status_effect("BLEED", PLAYER_ID, 2) - end - else - -- Just hit with 5 damage - deal_damage(ctx.guid, PLAYER_ID, 5) - end - - return nil - end - } -}) - -register_status_effect("BLEED", { - name = "Bleed", - description = "Losing some red sauce.", - look = "Bld", - foreground = "#ff0000", - state = function(ctx) - return nil - end, - can_stack = false, - decay = DECAY_ONE, - rounds = 2, - callbacks = { - on_turn = function(ctx) - return nil - end - } -}) diff --git a/assets/scripts/equipment/_colors.lua b/assets/scripts/equipment/_colors.lua new file mode 100644 index 0000000..a496373 --- /dev/null +++ b/assets/scripts/equipment/_colors.lua @@ -0,0 +1,3 @@ +COLOR_GRAY = "#2f3e46" +COLOR_BLUE = "#219ebc" +COLOR_PURPLE = "#725e9c" \ No newline at end of file diff --git a/assets/scripts/equipment/_util.lua b/assets/scripts/equipment/_util.lua new file mode 100644 index 0000000..dccfd1e --- /dev/null +++ b/assets/scripts/equipment/_util.lua @@ -0,0 +1,25 @@ +function add_found_artifact_event(id, picture, description, choice_description) + register_event(id, { + name = "Found: " .. registered.artifact[id].name, + description = string.format("!!%s\n\n**You found something!** %s", picture or "artifact_chest.jpg", description), + choices = { + { + description_fn = function() + return "Take " .. registered.artifact[id].name .. "... (" .. choice_description .. ")" + end, + callback = function(ctx) + give_artifact(id, PLAYER_ID) + return nil + end + }, { + description = "Leave...", + callback = function() + return nil + end + } + }, + on_end = function() + return GAME_STATE_RANDOM + end + }) +end \ No newline at end of file diff --git a/assets/scripts/equipment/arm_mounted_gun.lua b/assets/scripts/equipment/arm_mounted_gun.lua new file mode 100644 index 0000000..6b4b388 --- /dev/null +++ b/assets/scripts/equipment/arm_mounted_gun.lua @@ -0,0 +1,39 @@ +register_artifact("ARM_MOUNTED_GUN", { + name = "Arm Mounted Gun", + description = "Weapon that is mounted on your arm. It is very powerful.", + tags = { "ARM" }, + price = 190, + order = 0, + callbacks = { + on_pick_up = function(ctx) + clear_artifacts_by_tag("ARM", { ctx.guid }) + clear_cards_by_tag("ARM") + give_card("ARM_MOUNTED_GUN", PLAYER_ID) + return nil + end + } +}); + +register_card("ARM_MOUNTED_GUN", { + name = l("cards.ARM_MOUNTED_GUN.name", "Arm Mounted Gun"), + description = l("cards.ARM_MOUNTED_GUN.description", "Exhaust. Use your arm mounted gun to deal 15 (+3 for each upgrade) damage."), + state = function(ctx) + return string.format(l("cards.ARM_MOUNTED_GUN.state", "Use your arm mounted gun to deal %s damage."), highlight(7 + ctx.level * 3)) + end, + tags = { "ATK", "R", "T", "ARM" }, + max_level = 1, + color = COLOR_GRAY, + need_target = true, + does_exhaust = true, + point_cost = 3, + price = -1, + callbacks = { + on_cast = function(ctx) + deal_damage(ctx.caster, ctx.target, 7 + ctx.level * 3) + return nil + end + }, + test = function () + return assert_cast_damage("ARM_MOUNTED_GUN", 7) + end +}) \ No newline at end of file diff --git a/assets/scripts/equipment/basic_hand_weapons.lua b/assets/scripts/equipment/basic_hand_weapons.lua new file mode 100644 index 0000000..48dfd74 --- /dev/null +++ b/assets/scripts/equipment/basic_hand_weapons.lua @@ -0,0 +1,129 @@ +local hand_warning = "**Important:** If you already carry a artifact in your hand, you will have to drop it and related cards to pick up the new one." + +HAND_WEAPONS = { + { + id = "CROWBAR", + name = "Crowbar", + image = "red_room.jpg", + description = "A crowbar. It's a bit rusty, but it should still be useful!", + base_damage = 2, + base_cards = 3, + tags = { "ATK", "M", "T", "HND" }, + additional_cards = { "KNOCK_OUT" }, + price = 80 + }, + { + id = "VIBRO_KNIFE", + name = "VIBRO Knife", + description = "A VIBRO knife. Uses ultrasonic vibrations to cut through almost anything.", + base_damage = 3, + base_cards = 3, + tags = { "ATK", "M", "T", "HND" }, + additional_cards = { "VIBRO_OVERCLOCK" }, + price = 180 + }, + { + id = "LZR_PISTOL", + name = "LZR Pistol", + description = "A LZR pistol. Fires a concentrated beam of light.", + base_damage = 4, + base_cards = 3, + tags = { "ATK", "R", "T", "HND" }, + additional_cards = { "LZR_OVERCHARGE" }, + price = 280 + }, + { + id = "HAR_II", + name = "HAR-II", + description = "A HAR-II. A heavy assault rifle with a high rate of fire.", + base_damage = 5, + base_cards = 3, + tags = { "ATK", "R", "T", "HND" }, + additional_cards = { "HAR_BURST", "TARGET_PAINTER" }, + price = 380 + } +} + +HAND_WEAPONS_ARTIFACT_IDS = fun.iter(HAND_WEAPONS):map(function(w) return w.id end):totable() + +for _, weapon in pairs(HAND_WEAPONS) do + register_card(weapon.id, { + name = l("cards." .. weapon.id .. ".name", weapon.name), + description = l("cards." .. weapon.id .. ".description", string.format("Use to deal %s (+3 for each upgrade) damage.", weapon.base_damage)), + state = function(ctx) + return string.format(l("cards." .. weapon.id .. ".state", "Use to deal %s damage."), highlight(weapon.base_damage + ctx.level * 3)) + end, + tags = weapon.tags, + max_level = 3, + color = COLOR_GRAY, + need_target = true, + point_cost = 1, + price = 0, + callbacks = { + on_cast = function(ctx) + deal_damage(ctx.caster, ctx.target, weapon.base_damage + ctx.level * 3) + return nil + end + }, + test = function() + local dummy = add_actor_by_enemy("DUMMY") + local cards = get_cards(PLAYER_ID) + + -- Check if the card is in the player's hand + if not cards[1] then + return "Card not in hand" + end + + local card = get_card_instance(cards[1]) + if card.type_id ~= weapon.id then + return "Card has wrong type: " .. card.type_id + end + + cast_card(cards[1], dummy) + + if get_actor(dummy).hp ~= 100 - weapon.base_damage then + return "Expected " .. tostring(100 - weapon.base_damage) .. " health, got " .. get_actor(dummy).hp + end + + return nil + end + }) + + register_artifact(weapon.id, { + name = weapon.name, + description = weapon.description .. " Can be used in your hand.", + tags = weapon.tags, + price = weapon.price, + order = 0, + callbacks = { + on_pick_up = function(ctx) + clear_artifacts_by_tag("HND", { ctx.guid }) + clear_cards_by_tag("HND") + + -- add basic cards + for i = 1, weapon.base_cards do + give_card(weapon.id, PLAYER_ID) + end + + -- add additional cards + if weapon.additional_cards then + for _, card in pairs(weapon.additional_cards) do + give_card(card, PLAYER_ID) + end + end + + return nil + end + } + }) + + add_found_artifact_event(weapon.id, weapon.image, string.format("%s\n\n%s", weapon.description, hand_warning), registered.card[weapon.id].description) +end + +---hand_weapon_event returns a random hand weapon event weighted by price. +---@return string +function hand_weapon_event() + local ids = fun.iter(HAND_WEAPONS):map(function(w) return w.id end):totable() + local prices = fun.iter(HAND_WEAPONS):map(function(w) return 500 - w.price end):totable() + return choose_weighted(ids, prices) +end \ No newline at end of file diff --git a/assets/scripts/status_effects/block.lua b/assets/scripts/equipment/block.lua similarity index 65% rename from assets/scripts/status_effects/block.lua rename to assets/scripts/equipment/block.lua index 5c7b2f8..0124e04 100644 --- a/assets/scripts/status_effects/block.lua +++ b/assets/scripts/equipment/block.lua @@ -1,8 +1,28 @@ +register_card("BLOCK", { + name = "Block", + description = "Shield yourself and gain 5 " .. highlight("block") .. ".", + state = function(ctx) + return "Shield yourself and gain " .. highlight(1 + ctx.level) .. " block." + end, + tags = { "DEF" }, + max_level = 1, + color = COLOR_BLUE, + need_target = false, + point_cost = 1, + price = 40, + callbacks = { + on_cast = function(ctx) + give_status_effect("BLOCK", ctx.caster, 1 + ctx.level) + return nil + end + } +}) + register_status_effect("BLOCK", { name = "Block", description = "Decreases incoming damage for each stack", look = "Blk", - foreground = "#219ebc", + foreground = COLOR_BLUE, state = function(ctx) return "Takes " .. highlight(ctx.stacks) .. " less damage" end, @@ -12,12 +32,16 @@ register_status_effect("BLOCK", { order = 100, callbacks = { on_damage_calc = function(ctx) + if ctx.simulated then + return ctx.damage + end + if ctx.target == ctx.owner then add_status_effect_stacks(ctx.guid, -ctx.damage) return ctx.damage - ctx.stacks end return ctx.damage - end + end, }, test = function() return assert_chain({ diff --git a/assets/scripts/equipment/flash_bang.lua b/assets/scripts/equipment/flash_bang.lua new file mode 100644 index 0000000..c5ea1b1 --- /dev/null +++ b/assets/scripts/equipment/flash_bang.lua @@ -0,0 +1,49 @@ +register_card("FLASH_BANG", { + name = l("cards.FLASH_BANG.name", "Flash Bang"), + description = l("cards.FLASH_BANG.description", highlight("One-Time") .. "\n\nInflicts " .. highlight("Blinded") .. " on the target, causing them to deal less damage."), + tags = { "CC" }, + max_level = 0, + color = COLOR_PURPLE, + need_target = true, + does_consume = true, + point_cost = 1, + price = -1, + callbacks = { + on_cast = function(ctx) + give_status_effect("FLASH_BANG", ctx.target) + return nil + end + } +}) + +register_status_effect("FLASH_BANG", { + name = l("cards.FLASH_BANG.name", "Blinded"), + description = l("cards.FLASH_BANG.description", "Causing " .. highlight("25%") .. " less damage."), + look = "FL", + foreground = COLOR_PURPLE, + state = function(ctx) return nil end, + can_stack = true, + decay = DECAY_ONE, + rounds = 1, + callbacks = { + on_damage_calc = function(ctx) + if ctx.source == ctx.owner then + return ctx.damage * 0.75 + end + return ctx.damage + end + }, + test = function() + return assert_chain({ + function() return assert_status_effect_count(1) end, + function() return assert_status_effect("FLASH_BANG", 1) end, + function () + local dummy = add_actor_by_enemy("DUMMY") + local damage = deal_damage(PLAYER_ID, dummy, 10) + if damage ~= 7 then + return "Expected 7 damage, got " .. damage + end + end + }) + end +}) diff --git a/assets/scripts/equipment/knock_out.lua b/assets/scripts/equipment/knock_out.lua new file mode 100644 index 0000000..9439d30 --- /dev/null +++ b/assets/scripts/equipment/knock_out.lua @@ -0,0 +1,34 @@ +register_card("KNOCK_OUT", { + name = l("cards.KNOCK_OUT.name", "Knock Out"), + description = l("cards.KNOCK_OUT.description", "Inflicts " .. highlight("Knock Out") .. " on the target, causing them to miss their next turn."), + tags = { "CC" }, + max_level = 0, + color = COLOR_PURPLE, + need_target = true, + point_cost = 2, + price = -1, + callbacks = { + on_cast = function(ctx) + give_status_effect("KNOCK_OUT", ctx.target) + return nil + end + } +}) + +register_status_effect("KNOCK_OUT", { + name = l("status_effects.KNOCK_OUT.name", "Knock Out"), + description = l("status_effects.KNOCK_OUT.description", "Can't act"), + look = "K", + foreground = COLOR_PURPLE, + state = function(ctx) + return string.format(l("status_effects.KNOCK_OUT.state", "Can't act for %s turns"), highlight(ctx.stacks)) + end, + can_stack = true, + decay = DECAY_ONE, + rounds = 1, + callbacks = { + on_turn = function(ctx) + return true + end + } +}) diff --git a/assets/scripts/cards/melee_hit.lua b/assets/scripts/equipment/melee_hit.lua similarity index 53% rename from assets/scripts/cards/melee_hit.lua rename to assets/scripts/equipment/melee_hit.lua index aa10666..468938f 100644 --- a/assets/scripts/cards/melee_hit.lua +++ b/assets/scripts/equipment/melee_hit.lua @@ -1,19 +1,22 @@ register_card("MELEE_HIT", { name = l("cards.MELEE_HIT.name", "Melee Hit"), - description = l("cards.MELEE_HIT.description", "Use your bare hands to deal 5 (+3 for each upgrade) damage."), + description = l("cards.MELEE_HIT.description", "Use your bare hands to deal 1 (+1 for each upgrade) damage."), state = function(ctx) - return string.format(l("cards.MELEE_HIT.state", "Use your bare hands to deal %s damage."), highlight(5 + ctx.level * 3)) + return string.format(l("cards.MELEE_HIT.state", "Use your bare hands to deal %s damage."), highlight(1 + ctx.level)) end, - tags = { "ATK" }, + tags = { "ATK", "M", "HND" }, max_level = 1, - color = "#2f3e46", + color = COLOR_GRAY, need_target = true, point_cost = 1, - price = 30, + price = -1, callbacks = { on_cast = function(ctx) - deal_damage(ctx.caster, ctx.target, 5 + ctx.level * 3) + deal_damage(ctx.caster, ctx.target, 1 + ctx.level) return nil end - } + }, + test = function () + return assert_cast_damage("MELEE_HIT", 1) + end }) diff --git a/assets/scripts/events/act_0/enemies.lua b/assets/scripts/events/act_0/enemies.lua new file mode 100644 index 0000000..611a731 --- /dev/null +++ b/assets/scripts/events/act_0/enemies.lua @@ -0,0 +1,33 @@ +register_event("RUST_MITE", { + name = "Tasty metals...", + description = [[ + You come across a strange being. It seems to be eating the metal from the walls. It looks at you and after a few seconds it rushes towards you. It seems to be hostile. + ]], + tags = {"ACT_0"}, + choices = { + { + description = "Fight!", + callback = function() + add_actor_by_enemy("RUST_MITE") + return GAME_STATE_FIGHT + end + } + } +}) + +register_event("CLEAN_BOT", { + name = "Corpse. Clean. Engage.", + description = [[ + You come across a strange robot. It seems to be cleaning up the area. It looks at you and says "Corpse. Clean. Engage.". You're not sure what it means, but it doesn't seem to be friendly. + ]], + tags = {"ACT_0"}, + choices = { + { + description = "Fight!", + callback = function() + add_actor_by_enemy("CLEAN_BOT") + return GAME_STATE_FIGHT + end + } + } +}) diff --git a/assets/scripts/events/stage_0/start.lua b/assets/scripts/events/act_0/start.lua similarity index 65% rename from assets/scripts/events/stage_0/start.lua rename to assets/scripts/events/act_0/start.lua index 4a3150d..473e117 100644 --- a/assets/scripts/events/stage_0/start.lua +++ b/assets/scripts/events/act_0/start.lua @@ -13,43 +13,38 @@ As you struggle to gather your bearings, you notice a blinking panel on the wall **Shortly after you realize that you are not alone...**]], choices = { { - description = "Try to escape the facility before it finds you...", + description = "Try to find a weapon. " .. + highlight('Find meele weapon') .. " " .. highlight_warn("Take 4 damage"), callback = function() - -- Try to escape - if math.random() < 0.5 then - set_event(stage_1_init_events[math.random(#stage_1_init_events)]) - return GAME_STATE_EVENT - end + deal_damage(PLAYER_ID, PLAYER_ID, 4, true) + give_artifact( + choose_weighted_by_price(find_artifacts_by_tags({ "HND", "M" })), PLAYER_ID + ) - -- Let OnEnd handle the state change return nil end - }, { - description = "Gather your strength and attack it!", - callback = function() - return nil - end - } + }, + { + description = "Gather your strength and attack it!", + callback = function() + give_card("MELEE_HIT", PLAYER_ID) + give_card("MELEE_HIT", PLAYER_ID) + give_card("MELEE_HIT", PLAYER_ID) + + return nil + end + } }, on_enter = function() play_music("energetic_orthogonal_expansions") - - -- Give the player it's start cards - give_card("MELEE_HIT", PLAYER_ID) - give_card("MELEE_HIT", PLAYER_ID) - give_card("MELEE_HIT", PLAYER_ID) - give_card("MELEE_HIT", PLAYER_ID) - give_card("MELEE_HIT", PLAYER_ID) - - give_card("RUPTURE", PLAYER_ID) + end, + on_end = function() + actor_set_max_hp(PLAYER_ID, 10) + actor_set_hp(PLAYER_ID, 10) - give_card("BLOCK", PLAYER_ID) give_card("BLOCK", PLAYER_ID) give_card("BLOCK", PLAYER_ID) - give_artifact(random_artifact(150), PLAYER_ID) - end, - on_end = function() return GAME_STATE_RANDOM end }) diff --git a/assets/scripts/events/misc/raising_the_bar.lua b/assets/scripts/events/misc/raising_the_bar.lua deleted file mode 100644 index b9a6b96..0000000 --- a/assets/scripts/events/misc/raising_the_bar.lua +++ /dev/null @@ -1,45 +0,0 @@ -register_event("RAISING_THE_BAR", { - name = "Raising The Bar", - description = [[!!red_room.jpg - -...]], - choices = { - { - description_fn = function() - return "Take Crowbar... (" .. registered.card["CROWBAR"].description .. ")" - end, - callback = function(ctx) - give_card("CROWBAR", PLAYER_ID) - return nil - end - }, { - description = "Leave...", - callback = function() - return nil - end - } - }, - on_end = function() - return GAME_STATE_RANDOM - end -}) - -register_card("CROWBAR", { - name = "Crowbar", - description = "Deal " .. highlight(22) .. " damage.", - state = function(ctx) - return nil - end, - max_level = 0, - color = "#f37b21", - need_target = true, - exhaust = true, - point_cost = 3, - price = -1, - callbacks = { - on_cast = function(ctx) - deal_damage(ctx.caster, ctx.target, 22) - return nil - end - } -}) diff --git a/assets/scripts/events/misc/recycle_device.lua b/assets/scripts/events/misc/recycle_device.lua deleted file mode 100644 index 2db8d50..0000000 --- a/assets/scripts/events/misc/recycle_device.lua +++ /dev/null @@ -1,53 +0,0 @@ -register_event("RECYCLE_DEVICE", { - name = "Talking Being", - description = [[!!artifact_chest.jpg - -...]], - choices = { - { - description_fn = function() - return "Take Device... (" .. registered.card["RECYCLE"].description .. ")" - end, - callback = function(ctx) - give_card("RECYCLE", PLAYER_ID) - return nil - end - }, { - description = "Leave...", - callback = function() - return nil - end - } - }, - on_end = function() - return GAME_STATE_RANDOM - end -}) - -register_card("RECYCLE", { - name = "Recycle", - description = "Deal " .. - highlight(12) .. " damage. If " .. highlight("fatal") .. " upgrade random card. " .. highlight("Exhaust") .. - ".", - state = function(ctx) - return nil - end, - max_level = 0, - color = "#d8a448", - need_target = true, - exhaust = true, - point_cost = 2, - price = -1, - callbacks = { - on_cast = function(ctx) - local op_before = #get_opponent_guids(ctx.caster) - deal_damage(ctx.caster, ctx.target, 12) - - if op_before > #get_opponent_guids(ctx.caster) then - upgrade_random_card(ctx.caster) - end - - return nil - end - } -}) diff --git a/assets/scripts/events/misc/talking_being.lua b/assets/scripts/events/misc/talking_being.lua deleted file mode 100644 index 153b35b..0000000 --- a/assets/scripts/events/misc/talking_being.lua +++ /dev/null @@ -1,59 +0,0 @@ -register_event("TALKING_BEING", { - name = "Talking Being", - description = [[!!alien2.jpg - -Suddenly, a massive vine with a gaping, tooth-filled maw emerges from the shadows. It towers over you, its presence imposing and otherworldly. - -*"Hello, little one,"* the creature speaks in a deep, rumbling voice. *"I have been watching you. I see potential in you. I offer you a gift, something that will aid you on your journey."* - -You take a step back, unsure if you can trust this strange being. - -*"My blood,"* the creature says. *"It is not like any substance you have encountered before. It will grant you extraordinary abilities. But it demands a price. Some of your blood, in exchange for this gift."* - -The creature assures you that there are dangers to wielding such power and that it will change you in ways you cannot yet imagine. But the offer is tempting. Will you accept and risk the unknown, or do you refuse and potentially miss out on a powerful ally? - -**The decision is yours...**]], - choices = { - { - description_fn = function() - return "Offer blood... " .. text_italic("(deals " .. highlight(get_player().hp * 0.2) .. " damage)") - end, - callback = function(ctx) - actor_add_hp(PLAYER_ID, -get_player().hp * 0.2) - give_card("VINE_VOLLEY", PLAYER_ID) - give_card("VINE_VOLLEY", PLAYER_ID) - give_card("VINE_VOLLEY", PLAYER_ID) - return nil - end - }, { - description = "Leave...", - callback = function() - return nil - end - } - }, - on_end = function() - return GAME_STATE_RANDOM - end -}) - -register_card("VINE_VOLLEY", { - name = "Vine Volley", - description = "Deal " .. highlight("3x" .. tostring(3)) .. " damage.", - state = function(ctx) - return nil - end, - max_level = 0, - color = "#588157", - need_target = true, - point_cost = 1, - price = 100, - callbacks = { - on_cast = function(ctx) - deal_damage(ctx.caster, ctx.target, 3) - deal_damage(ctx.caster, ctx.target, 3) - deal_damage(ctx.caster, ctx.target, 3) - return nil - end - } -}) diff --git a/assets/scripts/events/stage_1/bio_kingdom.lua b/assets/scripts/events/stage_1/bio_kingdom.lua deleted file mode 100644 index bb3ade8..0000000 --- a/assets/scripts/events/stage_1/bio_kingdom.lua +++ /dev/null @@ -1,20 +0,0 @@ -register_event("BIO_KINGDOM", { - name = "Bio Kingdom", - description = [[!!plant_enviroment.jpg - -You finally find a way leading to the outside, and step out of the cryo facility into a world you no longer recognize. - -The air is thick with humidity and the sounds of the jungle are overwhelming. Strange, mutated plants tower over you, their vines twisting and tangling around each other in a macabre dance. The colors of the leaves and flowers are sickly, a greenish hue that reminds you of illness rather than life. The ruins of buildings are visible in the distance, swallowed up by the overgrowth. You can hear the chirping and buzzing of insects, but it's mixed with something else - something that sounds almost like whispers or moans. The "jungle" seems to be alive, but not in any way that you would have imagined.]], - choices = { - { - description = "Go...", - callback = function() - set_event("MERCHANT") - return GAME_STATE_EVENT - end - } - }, - on_end = function() - return GAME_STATE_RANDOM - end -}) diff --git a/assets/scripts/events/stage_1/the_core.lua b/assets/scripts/events/stage_1/the_core.lua deleted file mode 100644 index e2d12b5..0000000 --- a/assets/scripts/events/stage_1/the_core.lua +++ /dev/null @@ -1,20 +0,0 @@ -register_event("THE_CORE", { - name = "The Wasteland", - description = [[!!underground1.jpg - -You finally find a way you thought would lead to the outside, only to discover that you're still inside the massive facility known as *"The Core."* - -As you step out of the cryo facility, the eerie silence is broken by the sound of metal scraping against metal and distant whirring of malfunctioning machinery. The flickering lights and sparks from faulty wires cast a sickly glow on the cold metal walls. You realize that this place is not as deserted as you initially thought, and the unsettling feeling in your gut only grows stronger as you make your way through the dimly lit corridors, surrounded by the echoes of your own footsteps and the sound of flickering computer screens.]], - choices = { - { - description = "Go...", - callback = function() - set_event("MERCHANT") - return GAME_STATE_EVENT - end - } - }, - on_end = function() - return GAME_STATE_RANDOM - end -}) diff --git a/assets/scripts/events/stage_1/the_wasteland.lua b/assets/scripts/events/stage_1/the_wasteland.lua deleted file mode 100644 index 463a888..0000000 --- a/assets/scripts/events/stage_1/the_wasteland.lua +++ /dev/null @@ -1,20 +0,0 @@ -register_event("THE_WASTELAND", { - name = "The Wasteland", - description = [[!!dark_city1.jpg - -You finally find a way leading to the outside, and with a deep breath, you step out into the unforgiving wasteland. - -The scorching sun beats down on you as the sand whips against your skin, a reminder of the horrors that have befallen the world. In the distance, the remains of once-great cities jut up from the ground like jagged teeth, now nothing more than crumbling ruins. The air is thick with the acrid smell of decay and the oppressive silence is only broken by the occasional howl of some mutated creature. As you take your first steps into this new world, you realize that survival will not be easy, and that the journey ahead will be fraught with danger at every turn...]], - choices = { - { - description = "Go...", - callback = function() - set_event("MERCHANT") - return GAME_STATE_EVENT - end - } - }, - on_end = function() - return GAME_STATE_RANDOM - end -}) diff --git a/assets/scripts/status_effects/burn.lua b/assets/scripts/status_effects/burn.lua deleted file mode 100644 index b79f535..0000000 --- a/assets/scripts/status_effects/burn.lua +++ /dev/null @@ -1,18 +0,0 @@ -register_status_effect("BURN", { - name = "Burning", - description = "The enemy burns and receives damage.", - look = "Brn", - foreground = "#d00000", - state = function(ctx) - return "Takes " .. highlight(ctx.stacks * 4) .. " damage per turn" - end, - can_stack = true, - decay = DECAY_ALL, - rounds = 1, - callbacks = { - on_turn = function(ctx) - deal_damage(ctx.guid, ctx.owner, ctx.stacks * 2, true) - return nil - end - } -}) diff --git a/assets/scripts/status_effects/fear.lua b/assets/scripts/status_effects/fear.lua deleted file mode 100644 index 737831d..0000000 --- a/assets/scripts/status_effects/fear.lua +++ /dev/null @@ -1,17 +0,0 @@ -register_status_effect("FEAR", { - name = "Fear", - description = "Can't act.", - look = "Fear", - foreground = "#bb3e03", - state = function(ctx) - return "Can't act for " .. highlight(ctx.stacks) .. " turns" - end, - can_stack = true, - decay = DECAY_ONE, - rounds = 1, - callbacks = { - on_turn = function(ctx) - return true - end - } -}) diff --git a/assets/scripts/status_effects/strength.lua b/assets/scripts/status_effects/strength.lua deleted file mode 100644 index 89792cf..0000000 --- a/assets/scripts/status_effects/strength.lua +++ /dev/null @@ -1,20 +0,0 @@ -register_status_effect("STRENGTH", { - name = "Strength", - description = "Increases damage for each stack", - look = "Str", - foreground = "#d00000", - state = function(ctx) - return "Deal " .. highlight(ctx.stacks) .. " more damage" - end, - can_stack = true, - decay = DECAY_ALL, - rounds = 1, - callbacks = { - on_damage_calc = function(ctx) - if ctx.source == ctx.owner then - return ctx.damage + ctx.stacks - end - return ctx.damage - end - } -}) diff --git a/assets/scripts/status_effects/vulnerable.lua b/assets/scripts/status_effects/vulnerable.lua deleted file mode 100644 index 5666abb..0000000 --- a/assets/scripts/status_effects/vulnerable.lua +++ /dev/null @@ -1,20 +0,0 @@ -register_status_effect("VULNERABLE", { - name = "Vulnerable", - description = "Increases received damage for each stack", - look = "Vur", - foreground = "#ffba08", - state = function(ctx) - return "Takes " .. highlight(ctx.stacks * 25) .. "% more damage" - end, - can_stack = true, - decay = DECAY_ONE, - rounds = 1, - callbacks = { - on_damage_calc = function(ctx) - if ctx.target == ctx.owner then - return ctx.damage * (1.0 + 0.25 * ctx.stacks) - end - return ctx.damage - end - } -}) diff --git a/assets/scripts/status_effects/weaken.lua b/assets/scripts/status_effects/weaken.lua deleted file mode 100644 index 97fe653..0000000 --- a/assets/scripts/status_effects/weaken.lua +++ /dev/null @@ -1,20 +0,0 @@ -register_status_effect("WEAKEN", { - name = "Weaken", - description = "Weakens damage for each stack", - look = "W", - foreground = "#ed985f", - state = function() - return "Deals " .. highlight(ctx.stacks * 2) .. " less damage" - end, - can_stack = true, - decay = DECAY_ALL, - rounds = 1, - callbacks = { - on_damage_calc = function(ctx) - if ctx.source == ctx.owner then - return ctx.damage - ctx.stacks * 2 - end - return ctx.damage - end - } -}) diff --git a/assets/scripts/story_teller/act_0.lua b/assets/scripts/story_teller/act_0.lua new file mode 100644 index 0000000..12c8e35 --- /dev/null +++ b/assets/scripts/story_teller/act_0.lua @@ -0,0 +1,26 @@ + register_story_teller("ACT_0", { + active = function() + if #get_event_history() < 5 then + return 1 + end + return 0 + end, + decide = function() + local possible = find_events_by_tags({"ACT_0"}) + local history = get_event_history() + + print("possible") + print(possible) + print("history") + print(history) + + -- filter out events by id that have already been played + possible = fun.iter(possible):filter(function(event) + return not table.contains(history, event.id) + end):totable() + + set_event(possible[math.random(#possible)].id) + + return GAME_STATE_EVENT + end +}) diff --git a/assets/scripts/story_teller/stage_0.lua b/assets/scripts/story_teller/stage_0.lua deleted file mode 100644 index e43b118..0000000 --- a/assets/scripts/story_teller/stage_0.lua +++ /dev/null @@ -1,38 +0,0 @@ -stage_1_init_events = { "THE_CORE", "BIO_KINGDOM", "THE_WASTELAND" } - -register_story_teller("STAGE_0", { - active = function(ctx) - if not had_events_any(stage_1_init_events) then - return 1 - end - return 0 - end, - decide = function(ctx) - local stage = get_stages_cleared() - - if stage >= 3 then - -- If we didn't skip the pre-stage we get another artifact - set_event(create_artifact_choice({ random_artifact(get_merchant_gold_max()), random_artifact(get_merchant_gold_max()) }, { - description = [[As you explore the abandoned cryo facility, a feeling of dread washes over you. The facility is eerily quiet, with malfunctioning computers and flickering lights being the only signs of life. As you move through the winding corridors, you stumble upon a hidden door. It's almost as if the facility itself is trying to keep you from finding what lies beyond. - -After some effort, you manage to open the door and find yourself in a small room. The room is dark, and you can barely make out a chest in the center of the room. As you approach it, the feeling of unease grows stronger. What secret artifact could be hidden inside this chest? Is it something that will aid you on your journey or something more sinister? You take a deep breath, steeling yourself for whatever you may find inside, and reach for the lid...]], - on_end = function() - set_event(stage_1_init_events[math.random(#stage_1_init_events)]) - return GAME_STATE_EVENT - end - })) - - return GAME_STATE_EVENT - end - - -- Fight against rust mites or clean bots - local d = math.random(2) - if d == 1 then - add_actor_by_enemy("RUST_MITE") - elseif d == 2 then - add_actor_by_enemy("CLEAN_BOT") - end - - return GAME_STATE_FIGHT - end -}) diff --git a/assets/scripts/story_teller/stage_1_bio_kingdom.lua b/assets/scripts/story_teller/stage_1_bio_kingdom.lua deleted file mode 100644 index f8f0e69..0000000 --- a/assets/scripts/story_teller/stage_1_bio_kingdom.lua +++ /dev/null @@ -1,31 +0,0 @@ -stage_1_bio_kingdom = { - fights = { { "RUST_MITE", "RUST_MITE", "RUST_MITE" }, { "SHADOW_ASSASSIN", "SHADOW_ASSASSIN" }, { "SHADOW_ASSASSIN" } } -} - -register_story_teller("STAGE_1_BIO_KINGDOM", { - active = function(ctx) - if had_event("BIO_KINGDOM") then - return 1 - end - return 0 - end, - decide = function(ctx) - local stage = get_stages_cleared() - - if stage == 10 then - -- BOSS - end - - -- 10% chance to find a random artifact - if math.random() < 0.1 then - set_event(create_artifact_choice({ random_artifact(get_merchant_gold_max()), random_artifact(get_merchant_gold_max()) })) - end - - local choice = stage_1_bio_kingdom.fights[math.random(#stage_1_bio_kingdom.fights)] - for _, v in ipairs(choice) do - add_actor_by_enemy(v) - end - - return GAME_STATE_FIGHT - end -}) diff --git a/assets/scripts/story_teller/stage_1_the_core.lua b/assets/scripts/story_teller/stage_1_the_core.lua deleted file mode 100644 index 235f254..0000000 --- a/assets/scripts/story_teller/stage_1_the_core.lua +++ /dev/null @@ -1,29 +0,0 @@ -stage_1_the_core = { fights = { { "RUST_MITE", "RUST_MITE", "RUST_MITE" }, { "CLEAN_BOT", "CLEAN_BOT" } } } - -register_story_teller("STAGE_1_THE_CORE", { - active = function(ctx) - if had_event("THE_CORE") then - return 1 - end - return 0 - end, - decide = function(ctx) - local stage = get_stages_cleared() - - if stage == 10 then - -- BOSS - end - - -- 10% chance to find a random artifact - if math.random() < 0.1 then - set_event(create_artifact_choice({ random_artifact(get_merchant_gold_max()), random_artifact(get_merchant_gold_max()) })) - end - - local choice = stage_1_the_core.fights[math.random(#stage_1_the_core.fights)] - for _, v in ipairs(choice) do - add_actor_by_enemy(v) - end - - return GAME_STATE_FIGHT - end -}) diff --git a/assets/scripts/story_teller/stage_1_the_wasteland.lua b/assets/scripts/story_teller/stage_1_the_wasteland.lua deleted file mode 100644 index 9f9e462..0000000 --- a/assets/scripts/story_teller/stage_1_the_wasteland.lua +++ /dev/null @@ -1,29 +0,0 @@ -stage_1_the_wasteland = { fights = { { "SAND_STALKER" }, { "SAND_STALKER", "SAND_STALKER" } } } - -register_story_teller("STAGE_1_THE_WASTELAND", { - active = function(ctx) - if had_event("THE_WASTELAND") then - return 1 - end - return 0 - end, - decide = function(ctx) - local stage = get_stages_cleared() - - if stage == 10 then - -- BOSS - end - - -- 10% chance to find a random artifact - if math.random() < 0.1 then - set_event(create_artifact_choice({ random_artifact(get_merchant_gold_max()), random_artifact(get_merchant_gold_max()) })) - end - - local choice = stage_1_the_wasteland.fights[math.random(#stage_1_the_wasteland.fights)] - for _, v in ipairs(choice) do - add_actor_by_enemy(v) - end - - return GAME_STATE_FIGHT - end -}) diff --git a/assets/scripts/story_teller/stage_2.lua b/assets/scripts/story_teller/stage_2.lua deleted file mode 100644 index 95e1e33..0000000 --- a/assets/scripts/story_teller/stage_2.lua +++ /dev/null @@ -1,19 +0,0 @@ -register_story_teller("STAGE_2", { - active = function(ctx) - if had_events_any(stage_1_init_events) and get_stages_cleared() > 10 then - return 2 - end - return 0 - end, - decide = function(ctx) - local stage = get_stages_cleared() - - if stage == 20 then - -- BOSS - end - - add_actor_by_enemy("DUMMY") - - return GAME_STATE_FIGHT - end -}) diff --git a/assets/scripts/story_teller/stage_3.lua b/assets/scripts/story_teller/stage_3.lua deleted file mode 100644 index 96f415e..0000000 --- a/assets/scripts/story_teller/stage_3.lua +++ /dev/null @@ -1,19 +0,0 @@ -register_story_teller("STAGE_3", { - active = function(ctx) - if had_events_any(stage_1_init_events) and get_stages_cleared() > 20 then - return 3 - end - return 0 - end, - decide = function(ctx) - local stage = get_stages_cleared() - - if stage == 30 then - -- BOSS - end - - add_actor_by_enemy("DUMMY") - - return GAME_STATE_FIGHT - end -}) diff --git a/docs/LUA_API_DOCS.md b/docs/LUA_API_DOCS.md index 2c88e7b..2ebf76f 100644 --- a/docs/LUA_API_DOCS.md +++ b/docs/LUA_API_DOCS.md @@ -153,26 +153,26 @@ text_bold(value : any) -> string -
text_color
+
text_italic
-Makes the text foreground colored. Takes hex values like #ff0000. +Makes the text italic. **Signature:** ``` -text_color(color : string, value : any) -> string +text_italic(value : any) -> string ```
-
text_italic
+
text_red
-Makes the text italic. +Makes the text colored red. **Signature:** ``` -text_italic(value : any) -> string +text_red(value : any) -> string ```
@@ -326,7 +326,7 @@ get_fight() -> fight_state
get_fight_round
-Gets the number of stages cleared. +Gets the fight round. **Signature:** @@ -336,6 +336,18 @@ get_fight_round() -> number
+
get_stages_cleared
+ +Gets the number of stages cleared. + +**Signature:** + +``` +get_stages_cleared() -> number +``` + +
+
had_event
Checks if the event happened at least once. @@ -441,6 +453,30 @@ actor_add_max_hp(guid : guid, amount : number) -> None
+
actor_set_hp
+ +Sets the hp value of a actor to a number. This won't trigger any on_damage callbacks + +**Signature:** + +``` +actor_set_hp(guid : guid, amount : number) -> None +``` + +
+ +
actor_set_max_hp
+ +Sets the max hp value of a actor to a number. + +**Signature:** + +``` +actor_set_max_hp(guid : guid, amount : number) -> None +``` + +
+
add_actor_by_enemy
Creates a new enemy fighting against the player. Example ``add_actor_by_enemy("RUST_MITE")``. @@ -558,6 +594,18 @@ get_artifact_instance(guid : guid) -> artifact_instance
+
get_artifacts
+ +Returns all the artifacts guids from the given actor. + +**Signature:** + +``` +get_artifacts(actor_guid : string) -> guid[] +``` + +
+
give_artifact
Gives a actor a artifact. Returns the guid of the newly created artifact. @@ -791,7 +839,7 @@ None ### Functions
deal_damage
-Deal damage to a enemy from one source. If flat is true the damage can't be modified by status effects or artifacts. Returns the damage that was dealt. +Deal damage from one source to a target. If flat is true the damage can't be modified by status effects or artifacts. Returns the damage that was dealt. **Signature:** @@ -825,6 +873,18 @@ heal(source : guid, target : guid, amount : number) -> None
+
simulate_deal_damage
+ +Simulate damage from a source to a target. If flat is true the damage can't be modified by status effects or artifacts. Returns the damage that would be dealt. + +**Signature:** + +``` +simulate_deal_damage(source : guid, target : guid, damage : number, (optional) flat : boolean) -> number +``` + +
+ ## Player Operations Functions that are related to the player. diff --git a/game/artifact.go b/game/artifact.go index df0f7f3..c8e1215 100644 --- a/game/artifact.go +++ b/game/artifact.go @@ -13,6 +13,7 @@ type Artifact struct { ID string Name string Description string + Tags []string Order int Price int Callbacks map[string]luhelp.OwnedCallback diff --git a/game/card.go b/game/card.go index 80af6cd..d31c6b6 100644 --- a/game/card.go +++ b/game/card.go @@ -20,6 +20,7 @@ type Card struct { PointCost int MaxLevel int DoesExhaust bool + DoesConsume bool NeedTarget bool Price int Callbacks map[string]luhelp.OwnedCallback diff --git a/game/checkpoint.go b/game/checkpoint.go index 170c983..03d2b1c 100644 --- a/game/checkpoint.go +++ b/game/checkpoint.go @@ -17,10 +17,14 @@ func init() { type StateEvent string const ( - StateEventDeath = StateEvent("Death") - StateEventDamage = StateEvent("Damage") - StateEventHeal = StateEvent("Heal") - StateEventMoney = StateEvent("Money") + StateEventDeath = StateEvent("Death") + StateEventDamage = StateEvent("Damage") + StateEventHeal = StateEvent("Heal") + StateEventMoney = StateEvent("Money") + StateEventArtifactAdded = StateEvent("ArtifactAdded") + StateEventArtifactRemoved = StateEvent("ArtifactRemoved") + StateEventCardAdded = StateEvent("CardAdded") + StateEventCardRemoved = StateEvent("CardRemoved") ) type StateEventDeathData struct { @@ -45,6 +49,30 @@ type StateEventMoneyData struct { Money int } +type StateEventArtifactAddedData struct { + Owner string + GUID string + TypeID string +} + +type StateEventArtifactRemovedData struct { + Owner string + GUID string + TypeID string +} + +type StateEventCardAddedData struct { + Owner string + GUID string + TypeID string +} + +type StateEventCardRemovedData struct { + Owner string + GUID string + TypeID string +} + // StateCheckpoint saves the state of a session at a certain point. This can be used // to retroactively check what happened between certain actions. type StateCheckpoint struct { diff --git a/game/event.go b/game/event.go index 8b875d5..9c3579c 100644 --- a/game/event.go +++ b/game/event.go @@ -16,6 +16,7 @@ type Event struct { ID string Name string Description string + Tags []string Choices []EventChoice OnEnter luhelp.OwnedCallback OnEnd luhelp.OwnedCallback diff --git a/game/lua.go b/game/lua.go index 141c941..c252885 100644 --- a/game/lua.go +++ b/game/lua.go @@ -127,9 +127,9 @@ fun = require "fun" return 1 })) - d.Function("text_color", "Makes the text foreground colored. Takes hex values like #ff0000.", "string", "color : string", "value : any") - l.SetGlobal("text_color", l.NewFunction(func(state *lua.LState) int { - state.Push(lua.LString(removeAnsiReset(lipgloss.NewStyle().Foreground(lipgloss.Color(luhelp2.ToString(state.Get(1), mapper))).Render(luhelp2.ToString(state.Get(2), mapper))))) + d.Function("text_red", "Makes the text colored red.", "string", "value : any") + l.SetGlobal("text_red", l.NewFunction(func(state *lua.LState) int { + state.Push(lua.LString("\x1b[38;5;9m" + luhelp2.ToString(state.Get(1), mapper))) return 1 })) @@ -239,7 +239,7 @@ fun = require "fun" return 1 })) - d.Function("get_fight_round", "Gets the number of stages cleared.", "number") + d.Function("get_stages_cleared", "Gets the number of stages cleared.", "number") l.SetGlobal("get_stages_cleared", l.NewFunction(func(state *lua.LState) int { state.Push(lua.LNumber(session.GetStagesCleared())) return 1 @@ -333,12 +333,30 @@ fun = require "fun" return 0 })) + d.Function("actor_set_max_hp", "Sets the max hp value of a actor to a number.", "", "guid : guid", "amount : number") + l.SetGlobal("actor_set_max_hp", l.NewFunction(func(state *lua.LState) int { + session.UpdateActor(state.ToString(1), func(actor *Actor) bool { + actor.MaxHP = int(state.ToNumber(2)) + return true + }) + return 0 + })) + d.Function("actor_add_hp", "Increases the hp value of a actor by a number. Can be negative value to decrease it. This won't trigger any on_damage callbacks", "", "guid : guid", "amount : number") l.SetGlobal("actor_add_hp", l.NewFunction(func(state *lua.LState) int { session.ActorAddHP(state.ToString(1), int(state.ToNumber(2))) return 0 })) + d.Function("actor_set_hp", "Sets the hp value of a actor to a number. This won't trigger any on_damage callbacks", "", "guid : guid", "amount : number") + l.SetGlobal("actor_set_hp", l.NewFunction(func(state *lua.LState) int { + session.UpdateActor(state.ToString(1), func(actor *Actor) bool { + actor.HP = int(state.ToNumber(2)) + return true + }) + return 0 + })) + d.Function("add_actor_by_enemy", "Creates a new enemy fighting against the player. Example ``add_actor_by_enemy(\"RUST_MITE\")``.", "string", "enemy_guid : type_id") l.SetGlobal("add_actor_by_enemy", l.NewFunction(func(state *lua.LState) int { state.Push(lua.LString(session.AddActorFromEnemy(state.ToString(1)))) @@ -361,6 +379,12 @@ fun = require "fun" return 0 })) + d.Function("get_artifacts", "Returns all the artifacts guids from the given actor.", "guid[]", "actor_guid : string") + l.SetGlobal("get_artifacts", l.NewFunction(func(state *lua.LState) int { + state.Push(luhelp2.ToLua(state, session.GetArtifacts(state.ToString(1)))) + return 1 + })) + d.Function("get_artifact", "Returns the artifact definition. Can take either a guid or a typeId. If it's a guid it will fetch the type behind the instance.", "artifact", "id : string") l.SetGlobal("get_artifact", l.NewFunction(func(state *lua.LState) int { art, _ := session.GetArtifact(state.ToString(1)) @@ -483,7 +507,7 @@ fun = require "fun" d.Category("Damage & Heal", "Functions that deal damage or heal.", 10) - d.Function("deal_damage", "Deal damage to a enemy from one source. If flat is true the damage can't be modified by status effects or artifacts. Returns the damage that was dealt.", "number", "source : guid", "target : guid", "damage : number", "(optional) flat : boolean") + d.Function("deal_damage", "Deal damage from one source to a target. If flat is true the damage can't be modified by status effects or artifacts. Returns the damage that was dealt.", "number", "source : guid", "target : guid", "damage : number", "(optional) flat : boolean") l.SetGlobal("deal_damage", l.NewFunction(func(state *lua.LState) int { if state.GetTop() == 3 { state.Push(lua.LNumber(session.DealDamage(state.ToString(1), state.ToString(2), int(state.ToNumber(3)), false))) @@ -493,6 +517,16 @@ fun = require "fun" return 1 })) + d.Function("simulate_deal_damage", "Simulate damage from a source to a target. If flat is true the damage can't be modified by status effects or artifacts. Returns the damage that would be dealt.", "number", "source : guid", "target : guid", "damage : number", "(optional) flat : boolean") + l.SetGlobal("simulate_deal_damage", l.NewFunction(func(state *lua.LState) int { + if state.GetTop() == 3 { + state.Push(lua.LNumber(session.SimulateDealDamage(state.ToString(1), state.ToString(2), int(state.ToNumber(3)), false))) + } else { + state.Push(lua.LNumber(session.SimulateDealDamage(state.ToString(1), state.ToString(2), int(state.ToNumber(3)), bool(state.ToBool(4))))) + } + return 1 + })) + d.Function("deal_damage_multi", "Deal damage to multiple enemies from one source. If flat is true the damage can't be modified by status effects or artifacts. Returns a array of damages for each actor hit.", "number[]", "source : guid", "targets : guid[]", "damage : number", "(optional) flat : boolean") l.SetGlobal("deal_damage_multi", l.NewFunction(func(state *lua.LState) int { var guids []string diff --git a/game/resources.go b/game/resources.go index 6e053a8..54dcf4e 100644 --- a/game/resources.go +++ b/game/resources.go @@ -67,8 +67,8 @@ func NewResourcesManager(state *lua.LState, docs *ludoc.Docs, logger *log.Logger // Load all local scripts _ = fs.Walk("./assets/scripts", func(path string, isDir bool) error { - // Don't load libs - if strings.Contains(path, "scripts/libs") || strings.Contains(path, "scripts/definitions") { + // Don't load libs, definitions and paths containing two __ + if strings.Contains(path, "scripts/libs") || strings.Contains(path, "scripts/definitions") || strings.Contains(path, "__") { return nil } @@ -132,7 +132,10 @@ func (man *ResourcesManager) luaRegisterArtifact(l *lua.LState) int { man.log.Println("Registered artifact:", def.ID, def.Name) man.Artifacts[def.ID] = &def - man.registered.RawGetString("artifact").(*lua.LTable).RawSetString(def.ID, l.ToTable(2)) + + table := l.ToTable(2) + l.SetTable(table, lua.LString("id"), lua.LString(def.ID)) + man.registered.RawGetString("artifact").(*lua.LTable).RawSetString(def.ID, table) return 0 } @@ -151,7 +154,10 @@ func (man *ResourcesManager) luaRegisterCard(l *lua.LState) int { man.log.Println("Registered card:", def.ID, def.Name) man.Cards[def.ID] = &def - man.registered.RawGetString("card").(*lua.LTable).RawSetString(def.ID, l.ToTable(2)) + + table := l.ToTable(2) + l.SetTable(table, lua.LString("id"), lua.LString(def.ID)) + man.registered.RawGetString("card").(*lua.LTable).RawSetString(def.ID, table) return 0 } @@ -170,7 +176,10 @@ func (man *ResourcesManager) luaRegisterEnemy(l *lua.LState) int { man.log.Println("Registered enemy:", def.ID, def.Name) man.Enemies[def.ID] = &def - man.registered.RawGetString("enemy").(*lua.LTable).RawSetString(def.ID, l.ToTable(2)) + + table := l.ToTable(2) + l.SetTable(table, lua.LString("id"), lua.LString(def.ID)) + man.registered.RawGetString("enemy").(*lua.LTable).RawSetString(def.ID, table) return 0 } @@ -187,7 +196,10 @@ func (man *ResourcesManager) luaRegisterEvent(l *lua.LState) int { man.log.Println("Registered event:", def.ID, def.Name) man.Events[def.ID] = &def - man.registered.RawGetString("event").(*lua.LTable).RawSetString(def.ID, l.ToTable(2)) + + table := l.ToTable(2) + l.SetTable(table, lua.LString("id"), lua.LString(def.ID)) + man.registered.RawGetString("event").(*lua.LTable).RawSetString(def.ID, table) return 0 } @@ -206,7 +218,10 @@ func (man *ResourcesManager) luaRegisterStatusEffect(l *lua.LState) int { man.log.Println("Registered status_effect:", def.ID, def.Name) man.StatusEffects[def.ID] = &def - man.registered.RawGetString("status_effect").(*lua.LTable).RawSetString(def.ID, l.ToTable(2)) + + table := l.ToTable(2) + l.SetTable(table, lua.LString("id"), lua.LString(def.ID)) + man.registered.RawGetString("status_effect").(*lua.LTable).RawSetString(def.ID, table) return 0 } @@ -223,7 +238,10 @@ func (man *ResourcesManager) luaRegisterStoryTeller(l *lua.LState) int { man.log.Println("Registered story_teller:", def.ID) man.StoryTeller[def.ID] = &def - man.registered.RawGetString("story_teller").(*lua.LTable).RawSetString(def.ID, l.ToTable(2)) + + table := l.ToTable(2) + l.SetTable(table, lua.LString("id"), lua.LString(def.ID)) + man.registered.RawGetString("story_teller").(*lua.LTable).RawSetString(def.ID, table) return 0 } diff --git a/game/session.go b/game/session.go index e78c632..fa4c8cc 100644 --- a/game/session.go +++ b/game/session.go @@ -61,6 +61,12 @@ const ( DrawSize = 3 ) +type Hook string + +const ( + HookNextFightEnd = Hook("NextFightEnd") +) + // FightState represents the current state of the fight in regard to the // deck of the player. type FightState struct { @@ -107,6 +113,7 @@ type Session struct { eventHistory []string randomHistory []string ctxData map[string]any + hooks map[Hook][]func() loadedMods []string stateCheckpoints []StateCheckpoint @@ -125,11 +132,16 @@ func NewSession(options ...func(s *Session)) *Session { actors: map[string]Actor{ PlayerActorID: NewActor(PlayerActorID), }, - instances: map[string]any{}, - ctxData: map[string]any{}, + instances: map[string]any{}, + ctxData: map[string]any{}, + hooks: map[Hook][]func(){ + HookNextFightEnd: {}, + }, stagesCleared: 0, onLuaError: nil, luaErrors: make(chan LuaError, 25), + eventHistory: []string{}, + randomHistory: []string{}, } session.SetOnLuaError(nil) @@ -145,7 +157,6 @@ func NewSession(options ...func(s *Session)) *Session { session.resources = NewResourcesManager(session.luaState, session.luaDocs, session.log) session.resources.MarkBaseGame() session.loadMods(session.loadedMods) - session.SetEvent("START") session.log.Println("Session started!") @@ -156,6 +167,8 @@ func NewSession(options ...func(s *Session)) *Session { return true }) + session.SetEvent("START") + return session } @@ -331,12 +344,16 @@ func (s *Session) loadMods(mods []string) { } _ = fs.Walk(filepath.Join("./mods", mods[i]), func(path string, isDir bool) error { + if strings.Contains(path, "__") { + return nil + } + // If we find a locals folder we add it to the localization if isDir && filepath.Base(path) == "locals" { _ = localization.Global.AddFolder(path) } - if isDir && strings.HasSuffix(path, ".lua") { + if !isDir && strings.HasSuffix(path, ".lua") { luaBytes, err := fs.ReadFile(path) if err != nil { // TODO: error handling @@ -505,10 +522,10 @@ func (s *Session) FinishPlayerTurn() { for _, guid := range instanceKeys { switch instance := s.instances[guid].(type) { case StatusEffectInstance: - // If it was applied this round we never remove it. - if instance.RoundEntered == s.currentFight.Round { - continue - } + // TODO: investigate why this was here + // if instance.Owner == PlayerActorID && instance.RoundEntered == s.currentFight.Round { + // continue + // } se := s.resources.StatusEffects[instance.TypeID] @@ -625,6 +642,9 @@ func (s *Session) FinishFight() bool { } else { s.SetGameState(GameStateRandom) } + + // Trigger HookNextFightEnd + s.TriggerHooks(HookNextFightEnd) } return false } @@ -649,22 +669,28 @@ func (s *Session) FinishEvent(choice int) { if nextState != nil { if len(nextState.(string)) > 0 { s.SetGameState(GameState(nextState.(string))) + } else { + s.SetGameState(GameStateRandom) } - _, _ = event.OnEnd(CreateContext("type_id", event.ID, "choice", choice+1)) + _, _ = event.OnEnd.Call(CreateContext("type_id", event.ID, "choice", choice+1)) return } // Otherwise we allow OnEnd to dictate the new state - nextState, _ = event.OnEnd(CreateContext("type_id", event.ID, "choice", choice+1)) + nextState, _ = event.OnEnd.Call(CreateContext("type_id", event.ID, "choice", choice+1)) if nextState != nil && len(nextState.(string)) > 0 { s.SetGameState(GameState(nextState.(string))) + } else { + s.SetGameState(GameStateRandom) } return } - nextState, _ := event.OnEnd(CreateContext("type_id", event.ID, "choice", nil)) + nextState, _ := event.OnEnd.Call(CreateContext("type_id", event.ID, "choice", nil)) if nextState != nil && len(nextState.(string)) > 0 { s.SetGameState(GameState(nextState.(string))) + } else { + s.SetGameState(GameStateRandom) } } @@ -1282,6 +1308,14 @@ func (s *Session) GiveArtifact(typeId string, owner string) string { s.logLuaError(CallbackOnPickUp, instance.TypeID, err) } + s.PushState(map[StateEvent]any{ + StateEventArtifactAdded: StateEventArtifactAddedData{ + Owner: owner, + TypeID: typeId, + GUID: instance.GUID, + }, + }) + return instance.GUID } @@ -1293,6 +1327,14 @@ func (s *Session) RemoveArtifact(guid string) { } s.actors[instance.Owner].Artifacts.Remove(instance.GUID) delete(s.instances, guid) + + s.PushState(map[StateEvent]any{ + StateEventArtifactRemoved: StateEventArtifactRemovedData{ + Owner: instance.Owner, + TypeID: instance.TypeID, + GUID: instance.GUID, + }, + }) } // @@ -1333,6 +1375,15 @@ func (s *Session) GiveCard(typeId string, owner string) string { } s.instances[instance.GUID] = instance s.actors[owner].Cards.Add(instance.GUID) + + s.PushState(map[StateEvent]any{ + StateEventCardAdded: StateEventCardAddedData{ + Owner: owner, + TypeID: typeId, + GUID: instance.GUID, + }, + }) + return instance.GUID } @@ -1341,6 +1392,14 @@ func (s *Session) RemoveCard(guid string) { instance := s.instances[guid].(CardInstance) s.actors[instance.Owner].Cards.Remove(instance.GUID) delete(s.instances, guid) + + s.PushState(map[StateEvent]any{ + StateEventCardRemoved: StateEventCardRemovedData{ + Owner: instance.Owner, + TypeID: instance.TypeID, + GUID: instance.GUID, + }, + }) } // CastCard calls the OnCast callback for a card, casting it. @@ -1351,13 +1410,15 @@ func (s *Session) CastCard(guid string, target string) bool { s.logLuaError(CallbackOnCast, instance.TypeID, err) } if val, ok := res.(bool); ok { - if val { - TriggerCallbackSimple(s, CallbackOnActorDidCast, TriggerAll, EmptyContext, CreateContext("type_id", card.ID, "guid", guid, "caster", instance.Owner, "target", target, "level", instance.Level, "tags", card.Tags)) + if !val { + return false } - return val + + TriggerCallbackSimple(s, CallbackOnActorDidCast, TriggerAll, EmptyContext, CreateContext("type_id", card.ID, "guid", guid, "caster", instance.Owner, "target", target, "level", instance.Level, "tags", card.Tags)) + return true } } - return false + return true } // GetCards returns all cards owned by a actor. @@ -1398,7 +1459,8 @@ func (s *Session) PlayerCastHand(i int, target string) error { cardId := s.currentFight.Hand[i] // Only cast a card if castable and points are available and subtract them. - if card, _ := s.GetCard(cardId); card != nil { + card, _ := s.GetCard(cardId) + if card != nil { if !card.Callbacks[CallbackOnCast].Present() { return errors.New("card is not castable") } @@ -1418,11 +1480,15 @@ func (s *Session) PlayerCastHand(i int, target string) error { }) // Cast and exhaust if needed. - exhaust := s.CastCard(cardId, target) - if exhaust { - s.currentFight.Exhausted = append(s.currentFight.Exhausted, cardId) - } else { - s.currentFight.Used = append(s.currentFight.Used, cardId) + didCast := s.CastCard(cardId, target) + if didCast { + if card.DoesExhaust { + s.currentFight.Exhausted = append(s.currentFight.Exhausted, cardId) + } else if card.DoesConsume { + s.RemoveCard(cardId) + } else { + s.currentFight.Used = append(s.currentFight.Used, cardId) + } } s.FinishFight() @@ -1610,6 +1676,41 @@ func (s *Session) DealDamage(source string, target string, damage int, flat bool return damage } +// SimulateDealDamage will simulate damage to a target. If flat is true it will not trigger any callbacks which modify the damage. +func (s *Session) SimulateDealDamage(source string, target string, damage int, flat bool) int { + if _, ok := s.actors[source]; !ok { + return 0 + } + + _, ok := s.actors[target] + if !ok { + return 0 + } + + // If not flat we will modify the damage based on the OnDamageCalc callbacks. + if !flat { + reducer := func(cur float64, val float64) float64 { + return val + } + damage = int(TriggerCallbackReduce[float64]( + s, + CallbackOnDamageCalc, + TriggerAll, + reducer, + float64(damage), + "damage", + CreateContext("source", source, "target", target, "damage", damage, "simulated", true)), + ) + } + + // Negative damage aka heal is not allowed! + if damage < 0 { + return 0 + } + + return damage +} + // DealDamageMulti will deal damage to multiple targets and return the amount of damage dealt to each target. // If flat is true it will not trigger any OnDamageCalc callbacks which modify the damage. func (s *Session) DealDamageMulti(source string, targets []string, damage int, flat bool) []int { @@ -1699,7 +1800,7 @@ func (s *Session) GetActor(id string) Actor { return NewActor("") } -// UpdateActor updates an actor. +// UpdateActor updates an actor. If the update function returns true the actor will be updated. func (s *Session) UpdateActor(id string, update func(actor *Actor) bool) { actor := s.GetActor(id) if update(&actor) { @@ -1893,6 +1994,23 @@ func (s *Session) GivePlayerGold(amount int) { }) } +// +// Hooks +// + +// AddHook adds a hook to the session. +func (s *Session) AddHook(hook Hook, callback func()) { + s.hooks[hook] = append(s.hooks[hook], callback) +} + +// TriggerHooks triggers all hooks of a certain type. +func (s *Session) TriggerHooks(hook Hook) { + for _, callback := range s.hooks[hook] { + callback() + } + s.hooks[hook] = []func(){} +} + // // Misc Functions // diff --git a/ui/components/artifact.go b/ui/components/artifact.go index 19a6aa6..6bad55f 100644 --- a/ui/components/artifact.go +++ b/ui/components/artifact.go @@ -3,27 +3,35 @@ package components import ( "fmt" "github.com/BigJk/end_of_eden/game" + "github.com/BigJk/end_of_eden/ui" "github.com/BigJk/end_of_eden/ui/style" "github.com/charmbracelet/lipgloss" + "strings" ) var ( artifactStyle = lipgloss.NewStyle().Padding(1, 2).Margin(0, 2) ) -func ArtifactCard(session *game.Session, guid string, baseHeight int, maxHeight int) string { +func ArtifactCard(session *game.Session, guid string, baseHeight int, maxHeight int, optionalWidth ...int) string { art, _ := session.GetArtifact(guid) + width := 30 + if len(optionalWidth) > 0 { + width = optionalWidth[0] + } artifactStyle := artifactStyle.Copy(). - Width(30). + Width(width). Border(lipgloss.ThickBorder(), true, false, false, false). BorderBackground(lipgloss.Color("#495057")). BorderForeground(lipgloss.Color("#495057")). Background(lipgloss.Color("#343a40")). Foreground(style.BaseWhite) + tagsText := strings.Join(art.Tags, ", ") + return artifactStyle. Height(baseHeight). - Render(fmt.Sprintf("%s\n\n%s\n\n%s", style.BoldStyle.Render(art.Name), art.Description, lipgloss.NewStyle().Bold(true).Foreground(style.BaseYellow).Render(fmt.Sprintf("%d$", art.Price)))) + Render(fmt.Sprintf("%s\n\n%s\n\n%s", style.BoldStyle.Render(art.Name, strings.Repeat(" ", ui.Max(width-6-lipgloss.Width(art.Name)-lipgloss.Width(tagsText), 0)), tagsText), art.Description, lipgloss.NewStyle().Bold(true).Foreground(style.BaseYellow).Render(fmt.Sprintf("%d$", art.Price)))) } diff --git a/ui/components/card.go b/ui/components/card.go index 0123a0d..1f3077d 100644 --- a/ui/components/card.go +++ b/ui/components/card.go @@ -13,36 +13,44 @@ import ( var ( cardStyle = lipgloss.NewStyle().Padding(1, 2).Margin(0, 2) + headerStlye = lipgloss.NewStyle().Bold(true) cantCastStyle = lipgloss.NewStyle().Foreground(style.BaseRed) ) -func HalfCard(session *game.Session, guid string, active bool, baseHeight int, maxHeight int, minimal bool) string { +func HalfCard(session *game.Session, guid string, active bool, baseHeight int, maxHeight int, minimal bool, optionalWidth ...int) string { fight := session.GetFight() card, _ := session.GetCard(guid) canCast := fight.CurrentPoints >= card.PointCost cardState := session.GetCardState(guid) pointText := strings.Repeat("•", card.PointCost) - if !canCast { - pointText = cantCastStyle.Render(pointText) - } tagsText := strings.Join(card.Tags, ", ") cardCol, _ := colorful.Hex(card.Color) bgCol, _ := colorful.MakeColor(style.BaseGrayDarker) + width := 30 + if len(optionalWidth) > 0 { + width = optionalWidth[0] + } + cardStyle := cardStyle.Copy(). - Width(lo.Ternary(minimal && !active, 10, 30)). + Width(lo.Ternary(minimal && !active, 10, width)). Border(lipgloss.NormalBorder(), true, false, false, false). BorderBackground(lipgloss.Color(card.Color)). BorderForeground(lo.Ternary(active, style.BaseGray, lipgloss.Color(card.Color))). Background(lipgloss.Color(cardCol.BlendRgb(bgCol, 0.6).Hex())). Foreground(style.BaseWhite) + header := headerStlye.Render(fmt.Sprintf("%s%s%s", pointText, strings.Repeat(" ", ui.Max(width-4-lipgloss.Width(pointText)-lipgloss.Width(tagsText), 0)), tagsText)) + if !canCast { + header = cantCastStyle.Render(header) + } + if active { return cardStyle. Height(ui.Min(maxHeight-1, baseHeight+5)). - Render(fmt.Sprintf("%s%s%s\n\n%s\n\n%s", pointText, strings.Repeat(" ", 30-2-len(pointText)-len(tagsText)), tagsText, style.BoldStyle.Render(card.Name), cardState)) + Render(fmt.Sprintf("%s\n\n%s\n\n%s", header, style.BoldStyle.Render(card.Name), cardState)) } if minimal { @@ -53,6 +61,6 @@ func HalfCard(session *game.Session, guid string, active bool, baseHeight int, m return cardStyle. Height(baseHeight). - Render(fmt.Sprintf("%s%s%s\n\n%s\n\n%s", pointText, strings.Repeat(" ", 30-2-len(pointText)-len(tagsText)), tagsText, style.BoldStyle.Render(card.Name), cardState)) + Render(fmt.Sprintf("%s\n\n%s\n\n%s", header, style.BoldStyle.Render(card.Name), cardState)) } diff --git a/ui/menus/carousel/carousel.go b/ui/menus/carousel/carousel.go new file mode 100644 index 0000000..34c98e1 --- /dev/null +++ b/ui/menus/carousel/carousel.go @@ -0,0 +1,104 @@ +package carousel + +import ( + "github.com/BigJk/end_of_eden/ui" + "github.com/BigJk/end_of_eden/ui/style" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + zone "github.com/lrstanley/bubblezone" + "github.com/samber/lo" + "strings" +) + +const ( + ZoneLeftButton = "left_button" + ZoneRightButton = "right_button" + ZoneDoneButton = "done_button" +) + +type Model struct { + ui.MenuBase + + zones *zone.Manager + parent tea.Model + lastMouse tea.MouseMsg + title string + items []string + selected int +} + +func New(parent tea.Model, zones *zone.Manager, title string, items []string) Model { + return Model{ + zones: zones, + parent: parent, + title: title, + items: items, + } +} + +func (m Model) Init() tea.Cmd { + return nil +} + +func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.Size = msg + case tea.KeyMsg: + if msg.Type == tea.KeyEscape { + return m.parent, nil + } else if msg.Type == tea.KeyEnter { + return m.parent, nil + } else if msg.Type == tea.KeyLeft { + if m.selected > 0 { + m.selected-- + } + } else if msg.Type == tea.KeyRight { + if m.selected < len(m.items)-1 { + m.selected++ + } + } + case tea.MouseMsg: + m.LastMouse = msg + if msg.Type == tea.MouseLeft { + if m.zones.Get(ZoneLeftButton).InBounds(msg) { + if m.selected > 0 { + m.selected-- + } + } + if m.zones.Get(ZoneLeftButton).InBounds(msg) { + if m.selected < len(m.items)-1 { + m.selected++ + } + } + if m.zones.Get(ZoneDoneButton).InBounds(msg) { + return m.parent, nil + } + } + } + + return m, nil +} + +func (m Model) View() string { + title := style.BoldStyle.Copy().MarginBottom(4).Render(m.title) + + leftButton := m.zones.Mark(ZoneLeftButton, style.HeaderStyle.Copy().Background(lo.Ternary(m.zones.Get(ZoneLeftButton).InBounds(m.LastMouse), style.BaseRed, style.BaseRedDarker)).Margin(0, 2).Render("<--")) + rightButton := m.zones.Mark(ZoneRightButton, style.HeaderStyle.Copy().Background(lo.Ternary(m.zones.Get(ZoneRightButton).InBounds(m.LastMouse), style.BaseRed, style.BaseRedDarker)).Margin(0, 2).Render("-->")) + middle := lipgloss.JoinHorizontal(lipgloss.Center, + leftButton, + m.items[m.selected], + rightButton, + ) + + dots := lipgloss.NewStyle().Margin(2, 0).Render(strings.Join(lo.Map(m.items, func(item string, index int) string { + if index == m.selected { + return "●" + } + return "○" + }), " ")) + + doneButton := m.zones.Mark(ZoneDoneButton, style.HeaderStyle.Copy().Background(lo.Ternary(m.zones.Get(ZoneDoneButton).InBounds(m.LastMouse), style.BaseRed, style.BaseRedDarker)).MarginTop(2).Render("Continue")) + + return lipgloss.Place(m.Size.Width, m.Size.Height, lipgloss.Center, lipgloss.Center, lipgloss.JoinVertical(lipgloss.Center, title, middle, dots, doneButton)) +} diff --git a/ui/menus/eventview/eventview.go b/ui/menus/eventview/eventview.go index 4031105..49917ee 100644 --- a/ui/menus/eventview/eventview.go +++ b/ui/menus/eventview/eventview.go @@ -173,7 +173,7 @@ func (m Model) eventUpdateContent() Model { var chunks []string var mds []bool - lines := strings.Split(m.session.GetEvent().Description, "\n") + lines := strings.Split(strings.TrimSpace(m.session.GetEvent().Description), "\n") for i := range lines { if strings.HasPrefix(lines[i], "!!") { diff --git a/ui/menus/gameview/gameview.go b/ui/menus/gameview/gameview.go index 533d92a..e28eda7 100644 --- a/ui/menus/gameview/gameview.go +++ b/ui/menus/gameview/gameview.go @@ -6,6 +6,7 @@ import ( "github.com/BigJk/end_of_eden/system/audio" "github.com/BigJk/end_of_eden/ui" "github.com/BigJk/end_of_eden/ui/components" + "github.com/BigJk/end_of_eden/ui/menus/carousel" "github.com/BigJk/end_of_eden/ui/menus/eventview" "github.com/BigJk/end_of_eden/ui/menus/gameover" "github.com/BigJk/end_of_eden/ui/menus/merchant" @@ -39,11 +40,15 @@ type Model struct { animations []tea.Model ctrlDown bool + lastGameState game.GameState + lastEvent string + event tea.Model merchant tea.Model - Session *game.Session - Start game.StateCheckpointMarker + Session *game.Session + Start game.StateCheckpointMarker + BeforeStateSwitch game.StateCheckpointMarker } func New(parent tea.Model, zones *zone.Manager, session *game.Session) Model { @@ -55,8 +60,9 @@ func New(parent tea.Model, zones *zone.Manager, session *game.Session) Model { event: eventview.New(zones, session), merchant: merchant.New(zones, session), - Session: session, - Start: session.MarkState(), + Session: session, + Start: session.MarkState(), + BeforeStateSwitch: session.MarkState(), } } @@ -248,6 +254,52 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return gameover.New(m.zones, m.Session, m.Start), nil } + if m.Session.GetGameState() != m.lastGameState || m.Session.GetEventID() != m.lastEvent { + diff := m.BeforeStateSwitch.Diff(m.Session) + + m.BeforeStateSwitch = m.Session.MarkState() + m.lastGameState = m.Session.GetGameState() + m.lastEvent = m.Session.GetEventID() + + if len(diff) > 0 { + fmt.Println("DIFF", len(diff)) + + artifacts := lo.Map(lo.Filter(diff, func(item game.StateCheckpoint, index int) bool { + added, ok := item.Events[game.StateEventArtifactAdded].(game.StateEventArtifactAddedData) + return ok && !lo.SomeBy(diff, func(item game.StateCheckpoint) bool { + removed, ok := item.Events[game.StateEventArtifactRemoved].(game.StateEventArtifactRemovedData) + return ok && added.GUID == removed.GUID + }) + }), func(item game.StateCheckpoint, index int) string { + return components.ArtifactCard(m.Session, item.Events[game.StateEventArtifactAdded].(game.StateEventArtifactAddedData).GUID, 20, 20, 45) + }) + + cards := lo.Map(lo.Filter(diff, func(item game.StateCheckpoint, index int) bool { + added, ok := item.Events[game.StateEventCardAdded].(game.StateEventCardAddedData) + return ok && !lo.SomeBy(diff, func(item game.StateCheckpoint) bool { + removed, ok := item.Events[game.StateEventCardRemoved].(game.StateEventCardRemovedData) + return ok && added.GUID == removed.GUID + }) + }), func(item game.StateCheckpoint, index int) string { + return components.HalfCard(m.Session, item.Events[game.StateEventCardAdded].(game.StateEventCardAddedData).GUID, false, 20, 20, false, 45) + }) + + if len(artifacts) > 0 { + c := carousel.New(nil, m.zones, fmt.Sprintf("%d New Artifacts", len(artifacts)), artifacts) + c.Size = m.Size + cmds = append(cmds, root.Push(c)) + } + + if len(cards) > 0 { + c := carousel.New(nil, m.zones, fmt.Sprintf("%d New Cards", len(cards)), cards) + c.Size = m.Size + cmds = append(cmds, root.Push(c)) + } + } + + cmds = append(cmds, tea.ClearScreen) + } + return m, tea.Batch(cmds...) } diff --git a/ui/menus/overview/overview.go b/ui/menus/overview/overview.go index d3be557..d9b1771 100644 --- a/ui/menus/overview/overview.go +++ b/ui/menus/overview/overview.go @@ -99,6 +99,7 @@ func New(parent tea.Model, zones *zone.Manager, session *game.Session) MenuModel table.WithStyles(style.TableStyle), table.WithColumns([]table.Column{ {Title: "Name", Width: 25}, + {Title: "Tags", Width: 20}, {Title: "Level", Width: 5}, }), ), @@ -106,6 +107,7 @@ func New(parent tea.Model, zones *zone.Manager, session *game.Session) MenuModel table.WithStyles(style.TableStyle), table.WithColumns([]table.Column{ {Title: "Name", Width: 25}, + {Title: "Tags", Width: 20}, {Title: "Price", Width: 5}, }), ), @@ -141,14 +143,14 @@ func (m MenuModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // Update card table m.cardTable.SetRows(lo.Map(cards, func(guid string, index int) table.Row { card, instance := m.Session.GetCard(guid) - return table.Row{m.zones.Mark(ZoneCards+fmt.Sprint(index), card.Name), fmt.Sprint(instance.Level)} + return table.Row{m.zones.Mark(ZoneCards+fmt.Sprint(index), card.Name), strings.Join(card.Tags, ", "), fmt.Sprint(instance.Level)} })) m.cardTable.SetHeight(m.Size.Height - style.HeaderStyle.GetVerticalFrameSize() - 1 - 2) // Update artifact table m.artifactTable.SetRows(lo.Map(artifacts, func(guid string, index int) table.Row { art, _ := m.Session.GetArtifact(guid) - return table.Row{m.zones.Mark(ZoneArtifacts+fmt.Sprint(index), art.Name), fmt.Sprintf("%d$", art.Price)} + return table.Row{m.zones.Mark(ZoneArtifacts+fmt.Sprint(index), art.Name), strings.Join(art.Tags, ", "), fmt.Sprintf("%d$", art.Price)} })) m.artifactTable.SetHeight(m.Size.Height - style.HeaderStyle.GetVerticalFrameSize() - 1 - 2) @@ -162,15 +164,17 @@ func (m MenuModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m = m.updateLogViewport() - m.cardTable.SetWidth(m.contentWidth() - 40 - 2 - 2) + m.cardTable.SetWidth(m.contentWidth() - 55 - 4) m.cardTable.SetColumns([]table.Column{ - {Title: "Name", Width: m.contentWidth() - 40 - 2 - 2 - 10}, + {Title: "Name", Width: m.contentWidth() - 55 - 4 - 10 - 20 - 4}, + {Title: "Tags", Width: 20}, {Title: "Level", Width: 10}, }) - m.artifactTable.SetWidth(m.contentWidth() - 40 - 2 - 2) + m.artifactTable.SetWidth(m.contentWidth() - 55 - 4) m.artifactTable.SetColumns([]table.Column{ - {Title: "Name", Width: m.contentWidth() - 40 - 2 - 2 - 10}, + {Title: "Name", Width: m.contentWidth() - 55 - 4 - 10 - 20 - 4}, + {Title: "Tags", Width: 20}, {Title: "Price", Width: 10}, }) case tea.KeyMsg: @@ -279,7 +283,7 @@ func (m MenuModel) View() string { case ChoiceArtifacts: var selected string if m.artifactTable.Cursor() < len(m.Session.GetArtifacts(game.PlayerActorID)) { - selected = components.ArtifactCard(m.Session, m.Session.GetArtifacts(game.PlayerActorID)[m.artifactTable.Cursor()], 20, 40) + selected = components.ArtifactCard(m.Session, m.Session.GetArtifacts(game.PlayerActorID)[m.artifactTable.Cursor()], 20, 40, 45) } contentBox = contentStyle.Render(lipgloss.JoinVertical(lipgloss.Left, @@ -292,7 +296,7 @@ func (m MenuModel) View() string { case ChoiceCards: var selected string if m.artifactTable.Cursor() < len(m.Session.GetCards(game.PlayerActorID)) { - selected = components.HalfCard(m.Session, m.Session.GetCards(game.PlayerActorID)[m.cardTable.Cursor()], false, 20, 40, false) + selected = components.HalfCard(m.Session, m.Session.GetCards(game.PlayerActorID)[m.cardTable.Cursor()], false, 20, 40, false, 45) } contentBox = contentStyle.Render(lipgloss.JoinVertical(lipgloss.Left,