From 275460002e4c670c0007bfd1ab6d096b35880c21 Mon Sep 17 00:00:00 2001 From: Daniel Schmidt Date: Tue, 27 Aug 2024 23:14:00 +0200 Subject: [PATCH] feat: correct seeding --- assets/scripts/_util.lua | 24 ++++++--- assets/scripts/definitions/api.lua | 10 ++++ assets/scripts/enemies/_util.lua | 2 +- assets/scripts/enemies/cyber_slime.lua | 2 +- assets/scripts/events/act_0/enemies.lua | 20 +++---- assets/scripts/events/act_0/other.lua | 4 +- assets/scripts/libs/fun.lua | 6 +-- assets/scripts/story_teller/act_0.lua | 8 ++- cmd/game_win/main.go | 2 + docs/GAME_CONTENT_DOCS.md | 18 +++---- docs/LUA_API_DOCS.md | 70 +++++++++++++++++++------ game/lua.go | 14 +++++ game/saved_state.go | 7 ++- game/session.go | 47 ++++++++++++++--- go.mod | 4 +- system/image/image.go | 2 +- ui/menus/mainmenu/choices.go | 16 +++--- ui/menus/mainmenu/mainmenu.go | 13 +++-- 18 files changed, 198 insertions(+), 71 deletions(-) diff --git a/assets/scripts/_util.lua b/assets/scripts/_util.lua index 8b7065f..ee422ad 100644 --- a/assets/scripts/_util.lua +++ b/assets/scripts/_util.lua @@ -12,13 +12,13 @@ end ---highlight_warn some value with warning colors ---@param val any function highlight_warn(val) - return text_underline(text_bold(_escape_color("38;5;161") .. "[" .. tostring(val) .. "]" .. string.char(27) .. "[0m")) + return text_underline(text_bold(_escape_color("38;5;161") .. "[" .. tostring(val) .. "]" .. string.char(27) .. "[0m")) end ---highlight_success some value with success colors ---@param val any function highlight_success(val) - return text_underline(text_bold(_escape_color("38;5;119") .. "[" .. tostring(val) .. "]" .. string.char(27) .. "[0m")) + return text_underline(text_bold(_escape_color("38;5;119") .. "[" .. tostring(val) .. "]" .. string.char(27) .. "[0m")) end ---choose_weighted chooses an item from a list of choices, with a weight for each item. @@ -33,7 +33,7 @@ function choose_weighted(choices, weights) total_weight = total_weight + weight end - local random = math.random() * total_weight + local random = random() * total_weight for i, weight in ipairs(weights) do random = random - weight if random <= 0 then @@ -83,27 +83,39 @@ end ---@param tags string[] ---@return artifact[] function find_artifacts_by_tags(tags) - return find_by_tags(registered.artifact, tags) + local found = find_by_tags(registered.artifact, tags) + table.sort(found, function(a, b) return a.id:upper() < b.id:upper() end) + return found 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) + local found = find_by_tags(registered.card, tags) + table.sort(found, function(a, b) return a.id:upper() < b.id:upper() end) + return found 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) + local found = find_by_tags(registered.event, tags) + --table.sort(found, function(a, b) return a.id:upper() < b.id:upper() end) + return found 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) + table.sort(items, function(a, b) + if a.id == nil then + return a.type_id < b.type_id + end + return a.id < b.id + end) 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() diff --git a/assets/scripts/definitions/api.lua b/assets/scripts/definitions/api.lua index 093ef35..3088ca9 100644 --- a/assets/scripts/definitions/api.lua +++ b/assets/scripts/definitions/api.lua @@ -41,6 +41,16 @@ function fetch(key) end ---@return guid function guid() end +--- Returns a random number between 0 and 1 +---@return number +function random() end + +--- Returns a random number between min and max +---@param min number +---@param max number +---@return number +function random_int(min, max) end + --- Stores a persistent value for this run that will be restored after a save load. Can store any lua basic value or table. ---@param key string ---@param value any diff --git a/assets/scripts/enemies/_util.lua b/assets/scripts/enemies/_util.lua index ac6e67b..afbe13d 100644 --- a/assets/scripts/enemies/_util.lua +++ b/assets/scripts/enemies/_util.lua @@ -6,7 +6,7 @@ function cast_random(guid, target) if #cards == 0 then print("can't cast_random with zero cards available!") else - cast_card(cards[math.random(#cards)], target) + cast_card(cards[random_int(0, #cards)], target) end end diff --git a/assets/scripts/enemies/cyber_slime.lua b/assets/scripts/enemies/cyber_slime.lua index 2f5d28a..5a3cc12 100644 --- a/assets/scripts/enemies/cyber_slime.lua +++ b/assets/scripts/enemies/cyber_slime.lua @@ -40,7 +40,7 @@ register_enemy("CYBER_SLIME", { add_actor_by_enemy("CYBER_SLIME_MINION") add_actor_by_enemy("CYBER_SLIME_MINION") - if math.random() < 0.25 then + if random() < 0.25 then add_actor_by_enemy("CYBER_SLIME_MINION") end return nil diff --git a/assets/scripts/events/act_0/enemies.lua b/assets/scripts/events/act_0/enemies.lua index 7f87293..649b0c8 100644 --- a/assets/scripts/events/act_0/enemies.lua +++ b/assets/scripts/events/act_0/enemies.lua @@ -13,10 +13,10 @@ It seems to be eating the metal from the walls. It looks at you and after a few description = "Fight!", callback = function() add_actor_by_enemy("RUST_MITE") - if math.random() < 0.25 then + if random() < 0.25 then add_actor_by_enemy("RUST_MITE") end - if math.random() < 0.15 then + if random() < 0.15 then add_actor_by_enemy("REPAIR_DRONE") end return GAME_STATE_FIGHT @@ -41,10 +41,10 @@ It looks at you and says "Corpse. Clean. Engage.". description = "Fight!", callback = function() add_actor_by_enemy("CLEAN_BOT") - if math.random() < 0.25 then + if random() < 0.25 then add_actor_by_enemy("CLEAN_BOT") end - if math.random() < 0.15 then + if random() < 0.15 then add_actor_by_enemy("REPAIR_DRONE") end return GAME_STATE_FIGHT @@ -66,10 +66,10 @@ It seems to be waiting for its prey to come closer and there is no way around it description = "Fight!", callback = function() add_actor_by_enemy("CYBER_SPIDER") - if math.random() < 0.25 then + if random() < 0.25 then add_actor_by_enemy("CYBER_SPIDER") end - if math.random() < 0.15 then + if random() < 0.15 then add_actor_by_enemy("REPAIR_DRONE") end return GAME_STATE_FIGHT @@ -93,10 +93,10 @@ As you explore the facility, you hear a high-pitched whirring sound. A drone equ description = "Fight!", callback = function() add_actor_by_enemy("LASER_DRONE") - if math.random() < 0.10 then + if random() < 0.10 then add_actor_by_enemy("LASER_DRONE") end - if math.random() < 0.15 then + if random() < 0.15 then add_actor_by_enemy("REPAIR_DRONE") end return GAME_STATE_FIGHT @@ -120,7 +120,7 @@ As you delve deeper into the facility, you notice a bright glow emanating from a description = "Fight!", callback = function() add_actor_by_enemy("PLASMA_GOLEM") - if math.random() < 0.05 then + if random() < 0.05 then add_actor_by_enemy("REPAIR_DRONE") end return GAME_STATE_FIGHT @@ -144,7 +144,7 @@ As you explore the facility, you come across a strange cybernetic slime. It seem description = "Fight!", callback = function() add_actor_by_enemy("CYBER_SLIME") - if math.random() < 0.10 then + if random() < 0.10 then add_actor_by_enemy("REPAIR_DRONE") end return GAME_STATE_FIGHT diff --git a/assets/scripts/events/act_0/other.lua b/assets/scripts/events/act_0/other.lua index 70c6c5f..f8d4750 100644 --- a/assets/scripts/events/act_0/other.lua +++ b/assets/scripts/events/act_0/other.lua @@ -183,7 +183,7 @@ You find a room with a strange device in the middle. It seems to be some kind of :filter(function(card) return card.does_consume end):totable() - if math.random() < 0.5 then + if random() < 0.5 then local choosen = choose_weighted_by_price(possible_artifacts) if choosen then give_artifact(choosen, PLAYER_ID) @@ -232,7 +232,7 @@ You find a old automatic workstation. You are able to get it working again. You return nil end - local choosen = cards[math.random(#cards)] + local choosen = cards[random_int(0, #cards)] upgrade_card(choosen) deal_damage(PLAYER_ID, PLAYER_ID, 2, true) diff --git a/assets/scripts/libs/fun.lua b/assets/scripts/libs/fun.lua index db396a2..7b63af8 100644 --- a/assets/scripts/libs/fun.lua +++ b/assets/scripts/libs/fun.lua @@ -82,7 +82,7 @@ local string_gen = function(param, state) return state, r end -local ipairs_gen = ipairs({}) -- get the generating function from ipairs +local ipairs_gen = ipairs({}) -- get the generating function from ipairs local pairs_gen = pairs({ a = 0 }) -- get the generating function from pairs local map_gen = function(tab, key) @@ -262,11 +262,11 @@ end exports.ones = ones local rands_gen = function(param_x, _state_x) - return 0, math.random(param_x[1], param_x[2]) + return 0, random_int(param_x[1], param_x[2]) end local rands_nil_gen = function(_param_x, _state_x) - return 0, math.random() + return 0, random() end local rands = function(n, m) diff --git a/assets/scripts/story_teller/act_0.lua b/assets/scripts/story_teller/act_0.lua index 43e7a82..63e60d7 100644 --- a/assets/scripts/story_teller/act_0.lua +++ b/assets/scripts/story_teller/act_0.lua @@ -30,13 +30,17 @@ register_story_teller("_ACT_0", { if #possible == 0 then possible = find_events_by_tags({ "_ACT_0_FIGHT" }) end - set_event(possible[math.random(#possible)].id) + + local choosen = possible[random_int(0, #possible)] + if choosen ~= nil then + set_event(choosen.id) + end -- if we cleared a stage, give the player a random artifact local last_stage_count = fetch("last_stage_count") local current_stage_count = get_stages_cleared() if last_stage_count ~= current_stage_count then - local gets_random_artifact = math.random() < 0.25 + local gets_random_artifact = random() < 0.25 if gets_random_artifact then local player_artifacts = fun.iter(get_actor(PLAYER_ID).artifacts):map(function(id) diff --git a/cmd/game_win/main.go b/cmd/game_win/main.go index 5a5ad27..ff33758 100644 --- a/cmd/game_win/main.go +++ b/cmd/game_win/main.go @@ -78,6 +78,8 @@ func initSystems(hasAudio bool) { } func main() { + os.Setenv("EOE_IMG_TRUECOLOR", "1") + testArgs := testargs.New() flag.Parse() diff --git a/docs/GAME_CONTENT_DOCS.md b/docs/GAME_CONTENT_DOCS.md index d40311b..77a4da2 100644 --- a/docs/GAME_CONTENT_DOCS.md +++ b/docs/GAME_CONTENT_DOCS.md @@ -65,10 +65,10 @@ Content that is dynamically generated at runtime is not included in this documen ```mermaid pie title Action Points +"3 AP" : 2 "0 AP" : 12 "2 AP" : 1 "1 AP" : 6 -"3 AP" : 2 ``` @@ -107,7 +107,7 @@ title Card Types | ID | Name | Description | Initial HP | Max HP | Color | Used Callbacks | Test Present | |------------------------|-----------------------|-------------------------------------------------------------------|------------|--------|---------|------------------------------|-----------------| | ``CYBER_SPIDER`` | CYBER Spider | It waits for its prey to come closer | 8 | 8 | #ff4d6d | ``OnTurn`` | :no_entry_sign: | -| ``CLEAN_BOT`` | Cleaning Bot | It never stopped cleaning... | 13 | 13 | #32a891 | ``OnTurn``, ``OnPlayerTurn`` | :no_entry_sign: | +| ``CLEAN_BOT`` | Cleaning Bot | It never stopped cleaning... | 13 | 13 | #32a891 | ``OnPlayerTurn``, ``OnTurn`` | :no_entry_sign: | | ``CYBER_SLIME`` | Cyber Slime | A cybernetic slime that splits into smaller slimes when defeated. | 10 | 10 | #00ff00 | ``OnTurn``, ``OnActorDie`` | :no_entry_sign: | | ``CYBER_SLIME_MINION`` | Cyber Slime Offspring | A smaller version of the Cyber Slime. | 4 | 4 | #00ff00 | ``OnTurn`` | :no_entry_sign: | | ``DUMMY`` | Dummy | End me... | 100 | 100 | #deeb6a | ``OnTurn`` | :no_entry_sign: | @@ -122,21 +122,21 @@ title Card Types | ID | Name | Description | Tags | Choices | Test Present | |------------------------------|---------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------|----------------------------------------------------------------------------------------------------------------------------------|-----------------| | ``GAIN_GOLD_ACT_0`` | | ... - | _ACT_0 | | :no_entry_sign: | -| ``PLASMA_GOLEM`` | A glowing figure emerges... | As you delve deeper into the facility, you notice a bright glow emanating from a nearby chamber. A massive golem made of pure plasma energy steps into view. - **It looks ready to unleash its power!** - | _ACT_0_FIGHT | | :no_entry_sign: | -| ``LASER_DRONE`` | A menacing drone appears... | As you explore the facility, you hear a high-pitched whirring sound. A drone equipped with a powerful laser cannon appears in front of you. - **It looks ready to attack!** - | _ACT_0_FIGHT | | :no_entry_sign: | -| ``CYBER_SLIME`` | A strange cybernetic slime appears... | As you explore the facility, you come across a strange cybernetic slime. It seems to be pulsating with energy and looks hostile. - **Prepare for a fight!** - | _ACT_0_FIGHT | | :no_entry_sign: | +| ``PLASMA_GOLEM`` | A glowing figure emerges... | !!plasma_golem.jpg - As you delve deeper into the facility, you notice a bright glow emanating from a nearby chamber. A massive golem made of pure plasma energy steps into view. - **It looks ready to unleash its power!** - | _ACT_0_FIGHT | | :no_entry_sign: | +| ``LASER_DRONE`` | A menacing drone appears... | !!laser_drone.jpg - As you explore the facility, you hear a high-pitched whirring sound. A drone equipped with a powerful laser cannon appears in front of you. - **It looks ready to attack!** - | _ACT_0_FIGHT | | :no_entry_sign: | +| ``CYBER_SLIME`` | A strange cybernetic slime appears... | !!cyber_slime.jpg - As you explore the facility, you come across a strange cybernetic slime. It seems to be pulsating with energy and looks hostile. - **Prepare for a fight!** - | _ACT_0_FIGHT | | :no_entry_sign: | | ``MERCHANT`` | A strange figure | !!merchant.jpg - - The merchant is a tall, lanky figure draped in a long, tattered coat made of plant fibers and animal hides. Their face is hidden behind a mask made of twisted roots and vines, giving them an unsettling, almost alien appearance. - Despite their strange appearance, the merchant is a shrewd negotiator and a skilled trader. They carry with them a collection of bizarre and exotic items, including plant-based weapons, animal pelts, and strange, glowing artifacts that seem to pulse with an otherworldly energy. - The merchant is always looking for a good deal, and they're not above haggling with potential customers... | _ACT_0, _ACT_1, _ACT_2, _ACT_3 | | :no_entry_sign: | | ``CLEAN_BOT`` | Corpse. Clean. Engage. | !!clean_bot.jpg - While exploring the facility you hear a strange noise. Suddenly a strange robot appears from one of the corridors. - It seems to be cleaning up the area, but it's not working properly anymore and you can see small sparks coming out of it. - It looks at you and says "Corpse. Clean. Engage.". - **You're not sure what it means, but it doesn't seem to be friendly!** - | _ACT_0_FIGHT | | :no_entry_sign: | -| ``GAMBLE_1_ACT_0`` | Electro Barrier | You find a room with a strange device in the middle. It seems to be some kind of electro barrier protecting a storage container. You can either try to disable the barrier or leave. - | _ACT_0 | | :no_entry_sign: | +| ``GAMBLE_1_ACT_0`` | Electro Barrier | !!electro_barrier.jpg - You find a room with a strange device in the middle. It seems to be some kind of electro barrier protecting a storage container. You can either try to disable the barrier or leave. - | _ACT_0 | | :no_entry_sign: | | ``CROWBAR`` | Found: Crowbar | !!red_room.jpg - **You found something!** A crowbar. It's a bit rusty, but it should still be useful! - **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. | _ACT_0 | | :no_entry_sign: | | ``HAR_II`` | Found: HAR-II | !!artifact_chest.jpg - **You found something!** A HAR-II. A heavy assault rifle with a high rate of fire. - **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. | _ACT_1 | | :no_entry_sign: | | ``LZR_PISTOL`` | Found: LZR Pistol | !!artifact_chest.jpg - **You found something!** A LZR pistol. Fires a concentrated beam of light. - **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. | _ACT_1 | | :no_entry_sign: | | ``VIBRO_KNIFE`` | Found: VIBRO Knife | !!artifact_chest.jpg - **You found something!** A VIBRO knife. Uses ultrasonic vibrations to cut through almost anything. - **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. | _ACT_0 | | :no_entry_sign: | | ``GOLD_TO_HP_ACT_0`` | Old Vending Machine | You find an old vending machine, it seems to be still working. You can either pay 20 Gold to get 5 HP or leave. - | _ACT_0 | | :no_entry_sign: | -| ``RANDOM_ARTIFACT_ACT_0`` | Random Artifact | !!artifact_chest.jpg - You found a chest with a strange symbol on it. The chest is protected by a strange barrier. You can either open it and take some damage or leave. - | _ACT_0 | | :no_entry_sign: | +| ``RANDOM_ARTIFACT_ACT_0`` | Random Artifact | !!artifact_chest.jpg - You found a chest with a strange symbol on it. The chest is protected by a strange barrier. You can either open it and take some damage or leave. - | _ACT_0 | | :no_entry_sign: | | ``RANDOM_CONSUMEABLE_ACT_0`` | Random Consumeable | !!artifact_chest.jpg - You found a chest with a strange symbol on it. The chest is protected by a strange barrier. You can either open it and take some damage or leave. - | _ACT_0 | | :no_entry_sign: | -| ``MAX_LIFE_ACT_0`` | Symbiotic Parasite | You find a strange creature, it seems to be a symbiotic parasite. It offers to increase your max HP by 5. You can either accept or leave. - | _ACT_0 | | :no_entry_sign: | +| ``MAX_LIFE_ACT_0`` | Symbiotic Parasite | !!symbiotic_parasite.jpg - You find a strange creature, it seems to be a symbiotic parasite. It offers to increase your max HP by 5. You can either accept or leave. - | _ACT_0 | | :no_entry_sign: | | ``RUST_MITE`` | Tasty metals... | !!rust_mite.jpg - You are walking through the facility hoping to find a way out. After a few turns you hear a strange noise. You look around and 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!** - | _ACT_0_FIGHT | | :no_entry_sign: | -| ``UPRAGDE_CARD_ACT_0`` | Upgrade Station | You find a old automatic workstation. You are able to get it working again. You can either upgrade a random card or leave. - | _ACT_0 | | :no_entry_sign: | +| ``UPRAGDE_CARD_ACT_0`` | Upgrade Station | !!upgrade_station.jpg - You find a old automatic workstation. You are able to get it working again. You can either upgrade a random card or leave. - | _ACT_0 | | :no_entry_sign: | | ``START`` | Waking up... | !!cryo_start.jpg - You wake up in a dimly lit room, the faint glow of a red emergency light casting an eerie hue over the surroundings. The air is musty and stale, the metallic scent of the cryo-chamber still lingering in your nostrils. You feel groggy and disoriented, your mind struggling to process what's happening. - As you try to sit up, you notice that your body is stiff and unresponsive. It takes a few moments for your muscles to warm up and regain their strength. Looking around, you see that the walls are made of a dull gray metal, covered in scratches and scuff marks. There's a faint humming sound coming from somewhere, indicating that the facility is still operational. - You try to remember how you ended up here, but your memories are hazy and fragmented. The last thing you recall is a blinding flash of light and a deafening boom. You must have been caught in one of the nuclear explosions that devastated the world. - As you struggle to gather your bearings, you notice a blinking panel on the wall, with the words *"Cryo Sleep Malfunction"* displayed in bold letters. It seems that the system has finally detected the error that caused your prolonged slumber and triggered your awakening. - **Shortly after you realize that you are not alone...** | | | :no_entry_sign: | | ``CYBER_SPIDER`` | What is this thing at the ceiling? | !!cyber_spider.jpg - You come around a corner and see a strange creature hanging from the ceiling. It looks like a spider, but it's made out of metal. - It seems to be waiting for its prey to come closer and there is no way around it. - | _ACT_0_FIGHT | | :no_entry_sign: | diff --git a/docs/LUA_API_DOCS.md b/docs/LUA_API_DOCS.md index 10fc533..5a83239 100644 --- a/docs/LUA_API_DOCS.md +++ b/docs/LUA_API_DOCS.md @@ -1,4 +1,5 @@ # End Of Eden Lua Docs + ## Index - [Game Constants](#game-constants) @@ -23,6 +24,7 @@ General game constants. ### Globals +
DECAY_ALL
Status effect decays by all stacks per turn. @@ -67,7 +69,7 @@ Represents the random game state in which the active story teller will decide wh
PLAYER_ID
-Player actor id for use in functions where the guid is needed, for example: ``deal_damage(PLAYER_ID, enemy_guid, 10)``. +Player actor id for use in functions where the guid is needed, for example: `deal_damage(PLAYER_ID, enemy_guid, 10)`.
@@ -84,6 +86,7 @@ General game constants. None ### Functions +
fetch
Fetches a value from the persistent store @@ -108,6 +111,30 @@ guid() -> guid
+
random
+ +Returns a random number between 0 and 1 + +**Signature:** + +``` +random() -> number +``` + +
+ +
random_int
+ +Returns a random number between min and max + +**Signature:** + +``` +random_int(min : number, max : number) -> number +``` + +
+
store
Stores a persistent value for this run that will be restored after a save load. Can store any lua basic value or table. @@ -129,6 +156,7 @@ Helper functions for text styling. None ### Functions +
text_bg
Makes the text background colored. Takes hex values like #ff0000. @@ -186,6 +214,7 @@ Various logging functions. None ### Functions +
log_d
Log at **danger** level to player log. @@ -255,9 +284,10 @@ Audio helper functions. None ### Functions +
play_audio
-Plays a sound effect. If you want to play ``button.mp3`` you call ``play_audio("button")``. +Plays a sound effect. If you want to play `button.mp3` you call `play_audio("button")`. **Signature:** @@ -269,7 +299,7 @@ play_audio(sound : string) -> None
play_music
-Start a song for the background loop. If you want to play ``song.mp3`` you call ``play_music("song")``. +Start a song for the background loop. If you want to play `song.mp3` you call `play_music("song")`. **Signature:** @@ -288,6 +318,7 @@ Functions that modify the general game state. None ### Functions +
get_action_points_per_round
get the number of action points per round. @@ -441,6 +472,7 @@ Functions that modify or access the actors. Actors are either the player or enem None ### Functions +
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 @@ -491,7 +523,7 @@ 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")``. +Creates a new enemy fighting against the player. Example `add_actor_by_enemy("RUST_MITE")`. **Signature:** @@ -515,7 +547,7 @@ get_actor(guid : guid) -> actor
get_opponent_by_index
-Get opponent (actor) by index of a certain actor. ``get_opponent_by_index(PLAYER_ID, 2)`` would return the second alive opponent of the player. +Get opponent (actor) by index of a certain actor. `get_opponent_by_index(PLAYER_ID, 2)` would return the second alive opponent of the player. **Signature:** @@ -527,7 +559,7 @@ get_opponent_by_index(guid : guid, index : number) -> actor
get_opponent_count
-Get the number of opponents (actors) of a certain actor. ``get_opponent_count(PLAYER_ID)`` would return 2 if the player had 2 alive enemies. +Get the number of opponents (actors) of a certain actor. `get_opponent_count(PLAYER_ID)` would return 2 if the player had 2 alive enemies. **Signature:** @@ -539,7 +571,7 @@ get_opponent_count(guid : guid) -> number
get_opponent_guids
-Get the guids of opponents (actors) of a certain actor. If the player had 2 enemies, ``get_opponent_guids(PLAYER_ID)`` would return a table with 2 strings containing the guids of these actors. +Get the guids of opponents (actors) of a certain actor. If the player had 2 enemies, `get_opponent_guids(PLAYER_ID)` would return a table with 2 strings containing the guids of these actors. **Signature:** @@ -551,7 +583,7 @@ get_opponent_guids(guid : guid) -> guid[]
get_player
-Get the player actor. Equivalent to ``get_actor(PLAYER_ID)`` +Get the player actor. Equivalent to `get_actor(PLAYER_ID)` **Signature:** @@ -582,6 +614,7 @@ Functions that modify or access the artifacts. None ### Functions +
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. @@ -651,6 +684,7 @@ Functions that modify or access the status effects. None ### Functions +
add_status_effect_stacks
Adds to the stack count of a status effect. Negative values are also allowed. @@ -744,6 +778,7 @@ Functions that modify or access the cards. None ### Functions +
cast_card
Tries to cast a card with a guid and optional target. If the cast isn't successful returns false. @@ -849,6 +884,7 @@ Functions that deal damage or heal. None ### Functions +
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. @@ -918,6 +954,7 @@ Functions that are related to the player. None ### Functions +
finish_player_turn
Finishes the player turn. @@ -999,6 +1036,7 @@ Functions that are related to the merchant. None ### Functions +
add_merchant_artifact
Adds another random artifact to the merchant @@ -1037,7 +1075,7 @@ get_merchant() -> merchant_state
get_merchant_gold_max
-Returns the maximum value of artifacts and cards that the merchant will sell. Good to scale ``random_card`` and ``random_artifact``. +Returns the maximum value of artifacts and cards that the merchant will sell. Good to scale `random_card` and `random_artifact`. **Signature:** @@ -1056,6 +1094,7 @@ Functions that help with random generation. None ### Functions +
gen_face
Generates a random face. @@ -1101,10 +1140,11 @@ Functions that help with localization. None ### Functions +
l
-Returns the localized string for the given key. Examples on locals definition can be found in `/assets/locals`. Example: `` -l('cards.MY_CARD.name', "English Default Name")`` +Returns the localized string for the given key. Examples on locals definition can be found in `/assets/locals`. Example: ` +l('cards.MY_CARD.name', "English Default Name")` **Signature:** @@ -1123,6 +1163,7 @@ These functions are used to define new content in the base game and in mods. None ### Functions +
delete_base_game
Deletes all base game content. Useful if you don't want to include base game content in your mod. @@ -1343,7 +1384,7 @@ register_event("SOME_EVENT", description = "Go...", callback = function() -- If you return nil on_end will decide the next game state - return nil + return nil end }, { @@ -1353,7 +1394,7 @@ register_event("SOME_EVENT", }, on_enter = function() play_music("energetic_orthogonal_expansions") - + give_card("MELEE_HIT", PLAYER_ID) give_card("MELEE_HIT", PLAYER_ID) give_card("MELEE_HIT", PLAYER_ID) @@ -1435,7 +1476,7 @@ register_story_teller("STORY_TELLER_XYZ", { end -- Fight against rust mites or clean bots - local d = math.random(2) + local d = random_int(0, 2) if d == 1 then add_actor_by_enemy("RUST_MITE") elseif d == 2 then @@ -1454,4 +1495,3 @@ register_story_teller(id : type_id, definition : story_teller) -> None ```
- diff --git a/game/lua.go b/game/lua.go index 7ebdba6..6d1eef4 100644 --- a/game/lua.go +++ b/game/lua.go @@ -88,6 +88,20 @@ fun = require "fun" d.Category("Utility", "General game constants.", 1) + d.Function("random", "Returns a random number between 0 and 1. Prefer this function over math.random(), as this is seeded for the session.", "number") + l.SetGlobal("random", l.NewFunction(func(state *lua.LState) int { + state.Push(lua.LNumber(session.rand.Float64())) + return 1 + })) + + d.Function("random_int", "Returns a random number between min and max. Prefer this function over math.random(), as this is seeded for the session.", "number", "min : number", "max : number") + l.SetGlobal("random_int", l.NewFunction(func(state *lua.LState) int { + min := state.ToInt(1) + max := state.ToInt(2) + state.Push(lua.LNumber(session.rand.IntN(max-min) + min)) + return 1 + })) + d.Function("guid", "returns a new random guid.", "guid") l.SetGlobal("guid", l.NewFunction(func(state *lua.LState) int { state.Push(lua.LString(NewGuid("LUA"))) diff --git a/game/saved_state.go b/game/saved_state.go index 8435062..d96771a 100644 --- a/game/saved_state.go +++ b/game/saved_state.go @@ -1,6 +1,9 @@ package game -import "encoding/gob" +import ( + "encoding/gob" + "math/rand/v2" +) func init() { gob.Register(SavedState{}) @@ -10,6 +13,8 @@ func init() { // runtime or other pointer. type SavedState struct { State GameState + Seed uint64 + Rand *rand.PCG Actors map[string]Actor Instances map[string]any StagesCleared int diff --git a/game/session.go b/game/session.go index 9e3460b..f5e5a4c 100644 --- a/game/session.go +++ b/game/session.go @@ -8,7 +8,7 @@ import ( "fmt" "io" "log" - "math/rand" + "math/rand/v2" "path/filepath" "runtime" "sort" @@ -102,6 +102,9 @@ type Session struct { log *log.Logger luaState *lua.LState luaDocs *ludoc.Docs + seed uint64 + randSrc *rand.PCG + rand *rand.Rand resources *ResourcesManager state GameState @@ -128,9 +131,14 @@ type Session struct { // NewSession creates a new game session. func NewSession(options ...func(s *Session)) *Session { + seed := uint64(time.Now().UnixMilli()) + randSrc := rand.NewPCG(seed, 1337) session := &Session{ - log: log.New(io.Discard, "", 0), - state: GameStateEvent, + log: log.New(io.Discard, "", 0), + seed: seed, + randSrc: randSrc, + rand: rand.New(randSrc), + state: GameStateEvent, actors: map[string]Actor{ PlayerActorID: NewActor(PlayerActorID), }, @@ -163,10 +171,13 @@ func NewSession(options ...func(s *Session)) *Session { session.log.Println("Session started!") + session.log.Println("Seed:", session.seed) + session.Log(LogTypeSuccess, fmt.Sprintf("Seed: %d", seed)) + session.UpdatePlayer(func(actor *Actor) bool { - actor.HP = 80 - actor.MaxHP = 80 - actor.Gold = 50 + rand.Intn(50) + actor.HP = 100 + actor.MaxHP = 100 + actor.Gold = 0 return true }) @@ -201,6 +212,26 @@ func WithMods(mods []string) func(s *Session) { } } +// WithSeed sets the seed for the random number generator. +func WithSeed(seed uint64) func(s *Session) { + return func(s *Session) { + s.seed = seed + s.randSrc.Seed(seed, 1337) + } +} + +// WithSeedString sets the seed for the random number generator based on a string. +func WithSeedString(seed string) func(s *Session) { + return func(s *Session) { + generatedSeed := uint64(0) + for i, c := range seed { + generatedSeed += uint64(c) + uint64(i) + } + s.seed = generatedSeed + s.randSrc.Seed(generatedSeed, 1337) + } +} + // WithOnLuaError sets the function that will be called when a lua error happens. func WithOnLuaError(fn func(file string, line int, callback string, typeId string, err error)) func(s *Session) { return func(s *Session) { @@ -242,6 +273,8 @@ func (s *Session) LuaErrors() chan LuaError { func (s *Session) ToSavedState() SavedState { return SavedState{ State: s.state, + Seed: s.seed, + Rand: s.randSrc, Actors: s.actors, Instances: s.instances, StagesCleared: s.stagesCleared, @@ -261,6 +294,8 @@ func (s *Session) ToSavedState() SavedState { // should be loaded or the state could be corrupted. func (s *Session) LoadSavedState(save SavedState) { s.state = save.State + s.seed = save.Seed + s.randSrc = save.Rand s.actors = lo.MapValues(save.Actors, func(item Actor, key string) Actor { return item.Sanitize() }) diff --git a/go.mod b/go.mod index c51897f..d86b292 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/BigJk/end_of_eden -go 1.21 - -toolchain go1.21.6 +go 1.23.0 replace github.com/containerd/console => github.com/containerd/console v1.0.4-0.20230706203907-8f6c4e4faef5 diff --git a/system/image/image.go b/system/image/image.go index 0598f7a..08d4e1a 100644 --- a/system/image/image.go +++ b/system/image/image.go @@ -50,7 +50,7 @@ func buildOption(options ...Option) (Options, []imeji.Option) { data.tag += os.Getenv("EOE_IMG_PATTERN") } - if runtime.GOOS == "js" { + if runtime.GOOS == "js" || os.Getenv("EOE_IMG_TRUECOLOR") == "1" { imejiOptions = append(imejiOptions, imeji.WithTrueColor()) data.tag += "truecolor" } else { diff --git a/ui/menus/mainmenu/choices.go b/ui/menus/mainmenu/choices.go index 6e2e1f1..1c32b81 100644 --- a/ui/menus/mainmenu/choices.go +++ b/ui/menus/mainmenu/choices.go @@ -15,13 +15,14 @@ import ( type Choice string const ( - ChoiceWaiting = Choice("WAITING") - ChoiceContinue = Choice("CONTINUE") - ChoiceNewGame = Choice("NEW_GAME") - ChoiceAbout = Choice("ABOUT") - ChoiceSettings = Choice("SETTINGS") - ChoiceMods = Choice("MODS") - ChoiceExit = Choice("EXIT") + ChoiceWaiting = Choice("WAITING") + ChoiceContinue = Choice("CONTINUE") + ChoiceNewGame = Choice("NEW_GAME") + ChoiceNewGameSOD = Choice("NEW_GAME_SOD") + ChoiceAbout = Choice("ABOUT") + ChoiceSettings = Choice("SETTINGS") + ChoiceMods = Choice("MODS") + ChoiceExit = Choice("EXIT") ) type choiceItem struct { @@ -45,6 +46,7 @@ func NewChoicesModel(zones *zone.Manager, hideSettings bool) ChoicesModel { choices := []list.Item{ choiceItem{zones, "Continue", "Ready to continue dying?", ChoiceContinue}, choiceItem{zones, "New Game", "Start a new try.", ChoiceNewGame}, + choiceItem{zones, "New Game: Seed of the Day", "Start a new try with the daily seed.", ChoiceNewGameSOD}, choiceItem{zones, "About", "Want to know more?", ChoiceAbout}, choiceItem{zones, "Settings", "Other settings won't let you survive...", ChoiceSettings}, choiceItem{zones, "Mods", "Make the game even more fun!", ChoiceMods}, diff --git a/ui/menus/mainmenu/mainmenu.go b/ui/menus/mainmenu/mainmenu.go index 945a577..2752841 100644 --- a/ui/menus/mainmenu/mainmenu.go +++ b/ui/menus/mainmenu/mainmenu.go @@ -2,6 +2,11 @@ package mainmenu import ( "fmt" + "log" + "os" + "strings" + "time" + "github.com/BigJk/end_of_eden/game" "github.com/BigJk/end_of_eden/internal/fs" "github.com/BigJk/end_of_eden/system/audio" @@ -20,10 +25,6 @@ import ( "github.com/charmbracelet/lipgloss" zone "github.com/lrstanley/bubblezone" "github.com/samber/lo" - "log" - "os" - "strings" - "time" ) type Model struct { @@ -119,6 +120,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } + case ChoiceNewGameSOD: + fallthrough case ChoiceNewGame: audio.Play("btn_menu") @@ -133,12 +136,14 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return fmt.Sprintf("./mods/%s/images/", item) })...) + isSOD := m.choices.selected == ChoiceNewGameSOD m.choices = m.choices.Clear() return m, tea.Sequence( cmd, root.Push(gameview.New(m, m.zones, game.NewSession( game.WithLogging(log.New(f, "SESSION ", log.Ldate|log.Ltime|log.Lshortfile)), game.WithMods(m.settings.GetStrings("mods")), + lo.Ternary(isSOD, game.WithSeedString(time.Now().Format(time.DateOnly)), nil), lo.Ternary(os.Getenv("EOE_DEBUG") == "1", game.WithDebugEnabled(8272), nil), ))), )