diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..186bd39
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,15 @@
+# These are supported funding model platforms
+
+github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
+patreon: # Replace with a single Patreon username
+open_collective: # Replace with a single Open Collective username
+ko_fi: codefaux # Replace with a single Ko-fi username
+tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+liberapay: # Replace with a single Liberapay username
+issuehunt: # Replace with a single IssueHunt username
+lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
+polar: # Replace with a single Polar username
+buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
+thanks_dev: # Replace with a single thanks.dev username
+custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
diff --git a/.gitignore b/.gitignore
index 600d2d3..fbe68f1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,4 @@
-.vscode
\ No newline at end of file
+persistence.pdn
+persistence.code-workspace
+files/inspect.lua
+steam-update-persistence.sh
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..f0a83f5
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,24 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Launch Noita with debugger",
+ "type": "lua-local",
+ "request": "launch",
+ "program": {
+ "command": "C:/Abandon/Noita/noita_dev.exe",
+ "communication": "stdio"
+ },
+ // "args": ["-debug_lua", "-no-console", "-luadebugger"],
+ "stopOnEntry": true,
+ "cwd": "C:/Abandon/Noita/",
+ "env": {
+ "LOCAL_LUA_DEBUGGER_VSCODE": "1",
+ "DESTINATION_PATH": "mods/noita-vscode-debugger"
+ },
+ "verbose": true,
+ "pullBreakpointsSupport": true,
+ "scriptFiles": ["C:/Abandon/Noita/mods/noita-vscode-debugger/**/*.lua"],
+ }
+ ]
+}
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..916e5e5
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,48 @@
+{
+ "Lua.diagnostics.disable": [
+ "lowercase-global"
+ ],
+ "Lua.diagnostics.globals": [
+ "mod_dir",
+ "persistence_active",
+ "_nid",
+ "small_text_scale",
+ "gui",
+ "__layer",
+ "actions",
+ "actions_by_id",
+ "ACTION_TYPE_PROJECTILE",
+ "ACTION_TYPE_STATIC_PROJECTILE",
+ "Key_RETURN",
+ "ACTION_TYPE_MODIFIER",
+ "ACTION_TYPE_DRAW_MANY",
+ "ACTION_TYPE_MATERIAL",
+ "ACTION_TYPE_OTHER",
+ "ACTION_TYPE_UTILITY",
+ "ACTION_TYPE_PASSIVE",
+ "Key_LSHIFT",
+ "Key_GRAVE",
+ "Key_LCTRL",
+ "Mouse_wheel_down",
+ "Mouse_wheel_up",
+ "mod_setting_get_id",
+ "Key_ESCAPE",
+ "Key_TAB",
+ "reset_modifiers",
+ "ConfigGunShotEffects_Init",
+ "mod_setting_tooltip",
+ "mod_setting_handle_change_callback",
+ "AddGunActionPermanent",
+ "SetWandSprite",
+ "mod_setting_group_x_offset",
+ "MOD_SETTING_SCOPE_NEW_GAME",
+ "MOD_SETTING_SCOPE_RUNTIME",
+ "MOD_SETTING_SCOPE_RUNTIME_RESTART",
+ "mod_settings_get_version",
+ "mod_settings_update",
+ "mod_settings_gui",
+ "mod_settings_gui_count",
+ "loaded_profile_id",
+ "data_store_everyframe"
+ ]
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index 59f9945..3a4117b 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,67 @@
-# persistence
-a mod for Noita which adds persistence
+Add some Lite to your Rogue
+
+Credit to the original author; https://steamcommunity.com/sharedfiles/filedetails/?id=2144130266
+The mod has undergone a near full rewrite, but without their work this wouldn't have existed.
+The original author wishes to thank the great modding community on the official Noita Discord server https://discord.com/invite/SZtrP2r.
+Without them they would not been able to create this mod.
+I consider Discord an ephemeral information source, which is wasteful of the human resources supporting it. I used the wiki, github, and mods (example and otherwise) to acquire my knowledge.
+Informational efforts would be better suited on a wiki or other quasi-permanent platform than a user-required chat which by necessity fades to history and is fully lost.
+
+I'm happy to share any and all of my modding knowledge to anyone who asks.
+
+
+Finally, my github repo can be found at https://github.com/codefaux/persistence
+
+Now, on with the details:
+
+Persistence has configurable options in the Mod Options menu. Browse this menu to tune difficulty to your tastes, but defaults are a good starting point.
+
+There are five player profile slots. (Slot 5 is only accessible via Mod Options menu)
+
+'Persistence areas' have been added. Persistence areas count as the player's spawn location and Holy Mountains. The Persistence features described below will only work inside a Persistence Area.
+
+'Amnesiac Mode' has been added. This makes Persistence forget all changes upon the death of your character, to give a less Rogue-Lite experience, or to allow expensive testing/experimentation.
+
+Money:
+- An ingame money stash has been added. Each profile stores a separate money stash.
+- Mod Options can lock the stash, only allow deposits, or allow deposits and withdrawals.
+- Mod Options can assign a money payout from stash upon New Game, and upon reaching a Holy Mountain.
+- Mod Options configure how much money is kept on death. Default is 25%.
+- Persistence tracks your last known gold; polymorphed deaths still bank money properly.
+- Transfers into stash or player exceeding a specific balance will be skipped. This is intentional, to avoid overflows.
+
+Spells:
+- Spells can be researched using gold, to be permanently added to your profile's spell list.
+- Researched spells can be purchased for gold. Partially used spells with limited uses cannot be researched.
+- Spells can be recycled if not needed anymore. Recycled spells are simply deleted, there is no cost or benefit.
+- Mod Options can be used to scale spell research and purchase costs.
+- Spell 'Loadouts' can be stored from existing wands in your inventory, or cleared. Loadouts are shared across all profiles.
+- Loadouts must be named. Names must be 1-32 characters. Names do not need to be unique.
+- Stored Loadouts can be purchased. Purchased loadouts will fill your inventory.
+- KNOWN ISSUE: Due to an in-game bug, it requires -extensive- checking to avoid spells overlapping occasionally in inventory. Occasionally, spells will overlap until moved. Nothing will be lost.
+- KNOWN ISSUE: Due to limitations in string length, and how I must store spells, Spell Loadouts have a limited size which is difficult to predict.
+
+Wands:
+- Wands can be researched, storing the best stats you've researched in your profile.
+- Spells on a wand are destroyed when the wand is researched. Unresearched spells will not be automatically researched.
+- Wands can be created using researched stats from your profile.
+- Wands can be recycled if not needed. Recycled wands are simply deleted, there is no cost or benefit.
+- Unique wands cannot be fully researched. Their Type (special shape, name) will not be usable. Their stats will. This is intentional.
+- Wand Templates can be stored or updated during wand creation, or cleared.
+- Wand Templates can be purchased. Purchased wands drop at your location.
+- Wands created using Persistence can be modified.
+- KNOWN ISSUE: Sometimes when researching/recycling/purchasing a wand, the Inventory screen will show spells on the wrong wand, or on nothing at all. Close and re-open the Inventory window.
+
+
+A scanner runs while you explore the world. The scanner is a quality of life feature for Persistence, which can be disabled in Mod Options.
+- If you're standing on or immediately beside a spell or wand in the world, Persistence will indicate if it is unresearched.
+- Wands will indicate wether researched or not.
+- Spells will indicate wether researched or not.
+- Spells ON wands will only show if NOT researched.
+- Wands which only provide a new Type (graphic) will indicate separately.
+
+
+
+Many thanks to everyone using the mod, and doubly so to anyone who reports an issue, suggests a feature, or just says thanks.
+I'd like to thank people directly, but I don't want to violate anyone's privacy.
+If you've made a suggestion I've implemented or reported an issue and would like credit, contact me and tell me how to address you.
diff --git a/TODO.md b/TODO.md
new file mode 100644
index 0000000..2516382
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,108 @@
+-- ============
+-- BACKBURNER
+-- ============
+-- persistence tokens?
+-- Wand Icons don't line up consistently (best-effort, needs better fix)
+-- Mod order fixes?
+-- function EntityGetInRadius(pos_x, pos_y, radius) to auto close menu? -- EntityGetHerdRelationSafe( entity_a:int, entity_b:int )
+-- Spell per-purchase cost ramp options; per-spell, per-copy
+-- Mod setting for "allow research spells inside wands"?
+-- Translation table for translation mods, translation helper Mod Options setting for tooltips indicating strings and meanings
+-- Config option: Replace starter wand w/ templates 1, 2?
+-- Apply loadouts to replacement wands?
+-- Z order w/ blinking cursor, spell tooltip re: wand stats
+-- stash overflows; split into two variables, store w/ data_store into big_money, bigger_money?
+
+
+-- =====================
+-- BUGS / TOP PRIORITY
+-- =====================
+-- Options to disable persistent wand stat memory, spell memory, allowing per-run "fresh profile" with stashed money
+-- PRUNE DATASTORE/HELPER/WAND_SPELL_HELPER (second wave done)
+-- Enable in parallel worlds -- look for controls_mouse and controls_wasd for new "lobby" area?
+-- Flesh out and implement meta.lua rigorously
+-- Sort order drop-down
+-- Unresearched spells on wands not indicated in Research Wand menu
+
+-- ====================
+-- REQUESTS / IDEAS
+-- ====================
+-- "Builds" - Match template to Loadout
+-- check fit, can cast, etc
+-- auto-load spells onto wand when purhcased
+-- "Training" - Character stat buffs
+-- PlayerEntity;
+-- CharacterDataComponent
+-- fly_time_max=3 -- adjusts as expected
+-- flying_in_air_wait_frames==24
+-- CharacterPlatformingComponent
+-- velocity_min x, y=-57, -200
+-- velocity_max x, y=57, 350
+-- fly_velocity_x = ?
+-- fly_speed_max_up = ?
+-- swim_drag = 0.95
+-- swim_extra_horizontal_drag = 0.9
+-- pixel_gravity =
+-- DamageModelComponent
+-- damage_multipliers
+-- air_in_lungs_max
+-- IngestionComponent
+-- ingestion_capacity
+-- ingestion_reduce_every_n_frame
+-- KickComponent
+-- kick_radius
+-- player_kickforce
+-- kick_damage
+-- kick_knockback
+-- LightComponent
+-- radius
+-- MaterialSuckerComponent
+-- barrel_size
+-- num_cells_sucked_per_frame
+
+
+-- ====================
+-- DONE? NEEDS VERIFY
+-- ====================
+
+
+-- ==============
+-- KNOWN ISSUES
+-- ==============
+-- Spell overlap due to game item placement bug re: overfilled inventory. Worth fixing? Drop purchases on ground?
+-- Inventory spells on wands visibly offset due to wand changes. Seems ingame bug? Unlock during manipulation?
+-- Potential wand cost overlap in thoroughly broken gamestage. Clamp?
+-- Too easy to research "ultimate tier wand." Weighted research? One stat at a time?
+
+
+-- ==============
+-- for archival
+-- ==============
+-- function extract_action_stats(action)
+-- function draw_actions(_, _) end
+-- function add_projectile(x) proc_projectiles(x); end
+-- function add_projectile_trigger_hit_world(x, _) proc_projectiles(x); end
+-- function add_projectile_trigger_timer(x, _, _) proc_projectiles(x); end
+-- function add_projectile_trigger_death(x, _) proc_projectiles(x); end
+-- function check_recursion(_, x) return x or 0; end
+-- function move_discarded_to_deck() end
+-- function order_deck() end
+-- function StartReload() end
+-- function EntityGetWithTag(_) return {} end
+-- function GameGetFrameNum() return 5431289; end
+-- function SetRandomSeed() end
+-- function Random() return 1; end
+-- function GetUpdatedEntityID() return 0; end
+-- function EntityGetComponent(_) return {}; end
+-- function EntityGetFirstComponent(_, _) return {}; end
+-- function ComponentGetValue2(_) return 0; end
+-- function EntityGetTransform(_) return {}; end
+-- function EntityGetAllChildren(_) return {}; end
+-- function EntityGetInRadiusWithTag(_, _) return {}; end
+-- function GlobalsGetValue(_) return 0; end
+-- function GlobalsSetValue(_) end
+-- function find_the_wand_held() return nil; end
+-- function EntityGetFirstComponentIncludingDisabled(_) end
+
+-- local biome = BiomeMapGetName( pos_x, pos_y )
+-- if ( string.find( biome, "holymountain" ) == nil ) and ( string.find( biome, "victoryroom" ) == nil ) then
diff --git a/compatibility.xml b/compatibility.xml
index 8f5619d..f92e75e 100644
--- a/compatibility.xml
+++ b/compatibility.xml
@@ -1,2 +1,2 @@
-
+
diff --git a/config.lua b/config.lua
deleted file mode 100644
index 9039d47..0000000
--- a/config.lua
+++ /dev/null
@@ -1,31 +0,0 @@
-mod_config = {
- money_to_keep_on_death = 0.25,
- research_wand_price_multiplier = 1,
- research_spell_price_multiplier = 10,
- buy_wand_price_multiplier = 1,
- buy_spell_price_multiplier = 1,
- enable_edit_wands_in_lobby = true,
- enable_teleport_back_up = true,
- enable_menu_in_holy_mountain = false,
- reuseable_holy_mountain = false,
- spawn_location_as_lobby_location = false, -- false -> the lobby location will be relative to the mouse controlls stone; true -> the lobby location will be on the spawn location
- always_choose_save_id = -1, -- -1 -> choose manualy; 0 -> choose to not use the mod; 1-5 -> choose saveslot
- default_wands = {
- {
- name = "Handgun",
- file = "data/items_gfx/handgun.png",
- grip_x = 4,
- grip_y = 4,
- tip_x = 12,
- tip_y = 4
- },
- {
- name = "Bomb wand",
- file = "data/items_gfx/bomb_wand.png",
- grip_x = 4,
- grip_y = 4,
- tip_x = 12,
- tip_y = 4
- }
- }
-};
\ No newline at end of file
diff --git a/files/actions_by_id.lua b/files/actions_by_id.lua
new file mode 100644
index 0000000..83badb0
--- /dev/null
+++ b/files/actions_by_id.lua
@@ -0,0 +1,573 @@
+---how to use for your own mod: add a credit to me (@codefaux on github) in your credits file or UI credits screen. drop this file into files\ and call it with dofile() -- NOT dofile_once() -- in init.lua OnWorldPostUpdate()
+
+
+---only run function one time, but must init variable
+if actions_by_id__init_done~=true then
+ ---global fill-in for missing core function
+ actions_by_id_loaded=false;
+ math.sign = math.sign or function (x) if x<0 then return -1; else return 1; end; end
+
+ -- actions = actions or {};
+
+ ---variables and functions run/register/initialize only once
+ actions_by_id__notify_when_finished = true;
+
+ ---kill file cache, force reload
+ __loaded = {};
+
+ dofile_once( "data/scripts/gun/gun.lua" );
+ dofile_once( "data/scripts/lib/utilities.lua" );
+
+ ---init but don't overwrite public veriables
+ action_count = action_count or 0;
+ actions_by_id = actions_by_id or {};
+
+ ---returns nothing, directly acts on actions_by_id array
+ ---@param action_id string|table id of action to run OR entire action
+ function get_action_metadata(action_id)
+ if type(action_id)=="table" and action_id.id~=nil then action_id = action_id.id; end ---if passed action, switch to action_id string instead
+ if type(action_id)~="string" then return; end ---if still not string, exit
+
+ if actions_by_id[action_id].metadata~=nil and actions_by_id[action_id].c~=nil then return; end ---ensure action has not been simulated prior
+
+ reflecting = true; -- This is how we tell the game not to do the things, this redirects many of the action's calls to Reflection_RegisterProjectile() which allows us to extract data
+ metadata = {};
+
+ ---override Reflection_RegisterProjectile(xml) -in this context only-
+ Reflection_RegisterProjectile = function( projectile_xml )
+ local skip_or_modify_hash =
+ { ---list of member names we don't care about, or need to explicitly change
+ config = true,
+ config_explosion = true,
+ damage_critical = true,
+ damage_by_type = true,
+ mStartingLifetime = true,
+ mTriggers = true,
+ do_moveto_update = true,
+ angular_velocity = true,
+ penetrate_world_velocity_coeff = true,
+ ground_penetration_coeff = true,
+ -- mInitialSpeed = true,
+ on_death_emit_particle = true,
+ ground_collision_fx = true,
+ die_on_low_velocity = true,
+ on_collision_spawn_entity = true,
+ penetrate_entities = true,
+ velocity_sets_scale = true,
+ go_through_this_material = true,
+ die_on_liquid_collision = true,
+ bounce_at_any_angle = true,
+ -- damage_melee = true,
+ -- damage_explosion = true,
+ mDamagedEntities = true,
+ on_death_emit_particle_count = true,
+ -- never_hit_player = true,
+ camera_shake_when_shot = true,
+ -- bounce_always = true,
+ velocity_sets_y_flip = true,
+ -- damage_physics_hit = true,
+ -- collide_with_world = true,
+ -- penetrate_world = true,
+ physics_impulse_coeff = true,
+ mShooterHerdId = true,
+ lifetime_randomness = true,
+ on_death_particle_check_concrete = true,
+ spawn_entity_is_projectile = true,
+ -- explosion_dont_damage_shooter = true,
+ -- damage_ice = true,
+ muzzle_flash_file = true,
+ shoot_light_flash_g = true,
+ -- damage_radioactive = true,
+ shoot_light_flash_b = true,
+ collide_with_entities = true,
+ damage_scale_max_speed = true,
+ ragdoll_fx_on_collision = true,
+ bounces_left = true,
+ lifetime = true,
+ velocity_updates_animation = true,
+ -- damage_curse = true,
+ damage_every_x_frames = true,
+ friction = true,
+ on_death_duplicate_remaining = true,
+ -- damage_fire = true,
+ -- damage_projectile = true,
+ shoot_light_flash_radius = true,
+ damage_overeating = true,
+ -- projectiles = true,
+ die_on_low_velocity_limit = true,
+ shell_casing_material = true,
+ lob_min = true,
+ on_collision_die = true,
+ -- damage_drill = true,
+ -- on_death_explode = true,
+ lob_max = true,
+ on_collision_remove_projectile = true,
+ -- friendly_fire = true,
+ -- damage_scaled_by_speed = true,
+ -- damage_healing = true,
+ spawn_entity = true,
+ velocity_sets_rotation = true,
+ on_death_item_pickable_radius = true,
+ on_lifetime_out_explode = true,
+ play_damage_sounds = true,
+ mWhoShotEntityTypeID = true,
+ -- projectile_type = true,
+ -- damage_poison = true,
+ damage_game_effect_entities = true,
+ attach_to_parent_trigger = true,
+ speed_min = true,
+ speed_max = true,
+ on_death_gfx_leave_sprite = true,
+ ground_penetration_max_durability_to_destroy = true,
+ mLastFrameDamaged = true,
+ shoot_light_flash_r = true,
+ shell_casing_offset = true,
+ -- bounce_energy = true,
+ collide_with_tag = true,
+ ragdoll_force_multiplier = true,
+ damage = true,
+ collide_with_shooter_frames = true,
+ -- knockback_force = true,
+ velocity_sets_scale_coeff = true,
+ dont_collide_with_tag = true,
+ hit_particle_force_multiplier = true,
+ create_shell_casing = true,
+ bounce_fx_file = true,
+ collect_materials_to_shooter = true,
+ blood_count_multiplier = true,
+ mEntityThatShot = true,
+ mWhoShot = true,
+ -- damage_electricity = true,
+ on_death_emit_particle_type = true,
+ -- damage_slice = true,
+ -- damage_holy = true,
+ -- direction_nonrandom_rad = true,
+ direction_random_rad = true
+ };
+ -- local xml_entity_id = EntityLoadEndGameItem(projectile_xml, -2000000, -2000000);
+
+ -- local xml_entity_id = EntityCreateNew(); ---create new entity (not using EntityLoad intentionally)
+ -- EntityApplyTransform(xml_entity_id, -20000, -20000); ---move new entity really far away
+ -- EntityLoadToEntity(projectile_xml, xml_entity_id); ---load projectile entity with EntityLoadToEntity because "Does not load tags and other stuff." is better for smooth gameplay
+
+ -- if xml_entity_id~=nil and xml_entity_id~=0 then ---ensure loaded entity is 'valid'
+ -- local xml_component_pool = EntityGetAllComponents(xml_entity_id); ---gather components into pool for inspection
+ -- if xml_component_pool~=nil then ---if pool is valid
+ -- for _, xml_comp_id in pairs(xml_component_pool) do ---iterate pool for component ids
+ -- if xml_comp_id~=nil and xml_comp_id~=0 then ---ensure component is valid
+ -- EntitySetComponentIsEnabled(xml_entity_id, xml_comp_id, false); ---disable component for smoother gameplay
+ -- local xml_comp_name = ComponentGetTypeName(xml_comp_id); ---store component name, partially for reflection/debugging
+ -- if xml_comp_name=="ProjectileComponent" or xml_comp_name=="LightningComponent" or xml_comp_name=="ExplodeOnDamageComponent" or xml_comp_name=="ExplosionComponent" then ---only iterate on relevant components
+ -- local xml_comp_field_pool = ComponentGetMembers(xml_comp_id); ---gather components to pool for inspection
+ -- if xml_comp_field_pool~=nil then ---if pool is valid
+ -- metadata[projectile_xml] = metadata[projectile_xml] or {}; ---create empty table for incoming data if doesn't exist
+ -- metadata[projectile_xml][xml_comp_name] = metadata[projectile_xml][xml_comp_name] or {}; ---create empty table for incoming data if doesn't exist
+ -- for xml_comp_field, _ in pairs(xml_comp_field_pool) do ---iterate thru component members if they exist
+ -- if skip_or_modify_hash[xml_comp_field]~=true then ---only directly store members which aren't tagged
+ -- metadata[projectile_xml][xml_comp_name][xml_comp_field] = ComponentGetValue2(xml_comp_id, xml_comp_field); ---store member to structure
+ -- elseif xml_comp_field=="damage" then ---specific processing for "damage"
+ -- metadata[projectile_xml][xml_comp_name]["damage_basic"] = 25 * (ComponentGetValue2(xml_comp_id, xml_comp_field) or 0);
+ -- elseif xml_comp_field=="damage_by_type" then ---specific processing for "damage_by_type"
+ -- local dmg_type_pool = ComponentObjectGetMembers(xml_comp_id, xml_comp_field); ---gather damage_by_type members to pool for inspection
+ -- if dmg_type_pool~=nil then ---if pool is valid
+ -- for dmg_type, _ in pairs(dmg_type_pool) do ---break open damage_by_type table
+ -- metadata[projectile_xml][xml_comp_name]["damage_" .. dmg_type] = 25 * (ComponentObjectGetValue2(xml_comp_id, xml_comp_field, dmg_type) or 0); ---store in singles
+ -- end ---for dmg_type in damage_by_type
+ -- end--if damage_type_pool~-nil
+ -- elseif xml_comp_field=="config_explosion" then ---specific processing for "config_explosion"
+ -- metadata[projectile_xml][xml_comp_name]["damage_explosion"] = 25 * (ComponentObjectGetValue2(xml_comp_id, xml_comp_field, "damage") or 0); ---stored separately, standardise
+ -- metadata[projectile_xml][xml_comp_name]["explosion_radius"] = 25 * (ComponentObjectGetValue2(xml_comp_id, xml_comp_field, "explosion_radius") or 0); ---stored separately, standardise
+ -- elseif xml_comp_field=="mStartingLifetime" then ---specific processing for "mStartingLifetime"
+ -- metadata[projectile_xml][xml_comp_name]["lifetime"] = ComponentGetValue2(xml_comp_id, xml_comp_field); ---save with more typical name
+ -- elseif xml_comp_field=="direction_random_rad" then ---specific processing for "direction_random_rad"
+ -- metadata[projectile_xml][xml_comp_name]["spread_degrees"] = math.deg(ComponentGetValue2(xml_comp_id, xml_comp_field));
+ -- elseif xml_comp_field=="speed_min" then ---specific processing for "speed_min"
+ -- local in_speed = ComponentGetValue2(xml_comp_id, xml_comp_field); ---read speed_min value
+ -- metadata[projectile_xml][xml_comp_name]["speed_min"] = in_speed; ---save speed_min value
+ -- metadata[projectile_xml][xml_comp_name]["speed"] = (metadata[projectile_xml][xml_comp_name]["speed"]==nil) and in_speed or ((metadata[projectile_xml][xml_comp_name]["speed"] + in_speed) / 2); ---if speed exists, average against it. Else, store speed_min as speed.
+ -- elseif xml_comp_field=="speed_max" then ---specific processing for "speed_max"
+ -- local in_speed = ComponentGetValue2(xml_comp_id, xml_comp_field); -- read speed_max value
+ -- metadata[projectile_xml][xml_comp_name]["speed_max"] = in_speed; -- save speed_max value
+ -- metadata[projectile_xml][xml_comp_name]["speed"] = (metadata[projectile_xml][xml_comp_name]["speed"]==nil) and in_speed or ((metadata[projectile_xml][xml_comp_name]["speed"] + in_speed) / 2); ---if speed exists, average against it. Else, store speed_max as speed.
+ -- end ---if skip_or_modify_hash[proj_member] block
+ -- end ---for proj_member in ComponentGetMembers(proj_comp); ---iterate thru component members if they exist
+ -- metadata[projectile_xml][xml_comp_name].projectiles = 1; ---start with one projectile
+ -- end ---if xml_comp_field_pool~=nil
+ -- end ---if xml_comp_name=={relevant values}
+ -- EntityRemoveComponent(xml_entity_id, xml_comp_id); ---remove the projectile component before we kill it to avoid issues
+ -- end ---if xml_comp_id!=nil|0
+ -- end ---for xml_comp_id in xml_component_pool[]
+ -- end ---if xml_component_pool~=nil
+ -- EntityKill(xml_entity_id); ---kill the projectile entity since we're done with it
+ -- end ---if xml_entity_id~=nil|0
+ end -- function override Reflection_RegisterProjectile();
+
+ ---KEEP for later stubbing if required;
+ -- function draw_actions(_, _) end
+ -- function add_projectile(x) Reflection_RegisterProjectile(x); end
+ -- function add_projectile_trigger_hit_world(x, _) Reflection_RegisterProjectile(x); end
+ -- function add_projectile_trigger_timer(x, _, _) Reflection_RegisterProjectile(x); end
+ -- function add_projectile_trigger_death(x, _) Reflection_RegisterProjectile(x); end
+ -- function check_recursion(_, x) return x or 0; end
+ -- function move_discarded_to_deck() end
+ -- function order_deck() end
+ -- function StartReload() end
+ -- -- function EntityGetWithTag(_) return {} end
+ -- function GameGetFrameNum() return 5431289; end
+ -- function SetRandomSeed() end
+ -- function Random() return 1; end
+ -- -- function GetUpdatedEntityID() return 0; end
+ -- -- function EntityGetComponent(_) return {}; end
+ -- -- function EntityGetFirstComponent(_, _) return {}; end
+ -- -- function ComponentGetValue2(_) return 0; end
+ -- -- function EntityGetTransform(_) return {}; end
+ -- -- function EntityGetAllChildren(_) return {}; end
+ -- -- function EntityGetInRadiusWithTag(_, _) return {}; end
+ -- -- function GlobalsGetValue(_) return 0; end
+ -- function GlobalsSetValue(_) end
+ -- function find_the_wand_held() return nil; end
+ -- -- function EntityGetFirstComponentIncludingDisabled(_) end
+
+ ---strip values from c which we don't care about, modify others inline, return updated c table
+ ---@param in_c table incoming c table
+ ---@return table stripped c table
+ local function parse_c(in_c)
+ local skip_or_modify_hash =
+ { ---table of values to remove, modify here if add'l data required
+ -- pattern_degrees = true,
+ -- bounces = true,
+ -- action_spawn_level = true,
+ -- lightning_count = true,
+ state_destroyed_action = true,
+ -- action_ai_never_uses = true,
+ -- action_name = true,
+ -- recoil = true,
+ -- action_max_uses = true,
+ fire_rate_wait = true,
+ sprite = true,
+ -- explosion_damage_to_materials = true,
+ physics_impulse_coeff = true,
+ -- trail_material = true,
+ -- child_speed_multiplier = true,
+ -- action_draw_many_count = true,
+ -- damage_critical_chance = true,
+ gore_particles = true,
+ action_sprite_filename = true,
+ -- action_type = true,
+ game_effect_entities = true,
+ screenshake = true,
+ -- material = true,
+ extra_entities = true,
+ -- action_never_unlimited = true,
+ -- friendly_fire = true,
+ sound_loop_tag = true,
+ -- action_spawn_probability = true,
+ reload_time = true,
+ projectile_file = true,
+ state_shuffled = true,
+ -- explosion_radius = true,
+ custom_xml_file = true,
+ action_mana_drain = true, ---this value seems to be a lie
+ ragdoll_fx = true,
+ -- light = true,
+ -- action_is_dangerous_blast = true,
+ -- spread_degrees = true,
+ state_discarded_action = true,
+ action_unidentified_sprite_filename = true,
+ -- action_description = true,
+ -- speed_multiplier = true,
+ -- dampening = true,
+ -- damage_null_all = true,
+ -- knockback_force = true,
+ -- action_spawn_requires_flag = true,
+ -- blood_count_multiplier = true,
+ -- trail_material_amount = true,
+ -- damage_critical_multiplier = true,
+ state_cards_drawn = true,
+ action_spawn_manual_unlock = true,
+ -- material_amount = true,
+ -- action_id = true,
+ -- gravity = true,
+ -- lifetime_add = true,
+ -- damage_slice_add = true,
+ -- damage_ice_add = true,
+ -- damage_curse_add = true,
+ -- damage_healing_add = true,
+ -- damage_drill_add = true,
+ damage_fire_add = true,
+ damage_melee_add = true,
+ damage_electricity_add = true,
+ damage_explosion_add = true,
+ damage_projectile_add = true,
+ direction_random_rad = true,
+ };
+ local out_c = {}; ---create out_c table
+ for membername, _ in pairs(in_c) do ---iterate through members in incoming table
+ if skip_or_modify_hash[membername]~=true then ---check against skip_hash to only store desired info
+ out_c[membername] = in_c[membername]; ---copy member to output table
+ elseif membername=="damage_electricity_add" or ---specific processing for "damage_[...]_add"
+ membername=="damage_melee_add" or
+ membername=="damage_explosion_add" or
+ membername=="damage_projectile_add" or
+ membername=="damage_fire_add" then
+ out_c[membername] = in_c[membername] * 25; ---engine shows value*25 for damages/health
+ elseif membername=="reload_time" or ---specific processing for "reload_time" | "fire_rate_wait"
+ membername=="fire_rate_wait" then
+ out_c[membername] = in_c[membername] / 60; ---engine shows times in 60th of second
+ elseif membername=="direction_random_rad" then ---specific processing for "direction_random_rad"
+ out_c["spread_degrees"] = math.deg(in_c[membername]); ---engine stores angles in radians
+ end ---if skip_hash[membername];
+ end ---for membername in in_ic
+ return out_c; ---return data table
+ end ---function strip_c(in_c); ---returns stripped c structure
+
+ -- local _draw_actions = draw_actions;
+ local draws = 0; -- start at 0 additional draws
+ draw_actions = function( x ) draws = draws + x; end -- another local override for action() to count draw_action calls
+
+ local _c = c; -- capture the global c context
+ c = {}; -- clear c context so we only get one action()
+ shot_effects = {}; -- clear shot_effects context so we only get one action()
+ current_reload_time = 0; -- clear current_reload_time so we only get one action()
+ reset_modifiers( c ); -- prepare c table structure and initialize for relative operations
+ ConfigGunShotEffects_Init( shot_effects ); -- prepare shot_effects table structure and initialize for relative operations
+ actions_by_id[action_id].action(); -- call the action() function
+ actions_by_id[action_id] = actions_by_id[action_id] or {}; -- new table if not already present
+ actions_by_id[action_id].c = parse_c(c); -- strip c before storing its data
+ actions_by_id[action_id].c.draw_actions = draws; -- add a few flags
+ actions_by_id[action_id].c.reload_time = current_reload_time;
+ actions_by_id[action_id].c.recoil_knockback = shot_effects.recoil_knockback;
+ actions_by_id[action_id].metadata = metadata;
+ c = _c; -- restore the global c context
+ reflecting = false; -- Return to normal. Likely not necessary but....
+ end -- function get_action_metadata(action_id)
+
+ ---intended to be run when new actions are found, typically by code below -- feeds information directly into actions_by_id
+ ---@param max_new_actions_this_pass number
+ function collect_action_data(max_new_actions_this_pass)
+ local target_cnt = action_count + max_new_actions_this_pass; ---track how many actions we're targeting as our max
+ local player = EntityGetWithTag("player_unit")[1];
+
+ if player then EntityRemoveTag(player, "player_unit"); end ---remove player_unit tag, this protects us from some actions
+ for _,curr_action in pairs(actions) do ---iterate thru actions until finished or stopped
+ if actions_by_id[curr_action.id]==nil and curr_action.id~=nil then ---only process non-nil entries
+ action_count = (action_count or 0) + 1; ---add to the action count, start at zero if un-initialized
+ actions_by_id[curr_action.id] = curr_action; ---store current action basic data
+ get_action_metadata(curr_action.id); ---process action to add metadata
+ end ---if actions_by_id[curr_action.id]==nil and curr_action.id~=nil;
+ if action_count >= target_cnt then break; end
+ end ---for curr_action in actions;
+ if player then EntityAddTag(player, "player_unit"); end ---re-add player_unit tag to stored player entity
+ end -- function collect_action_data(max_new_actions_this_pass)
+
+ ---debugging function
+ function table_dump(o)
+ if type(o) == 'table' then
+ local s = '{ '
+ for k,v in pairs(o) do
+ if type(k) ~= 'number' then k = '"'..k..'"' end
+ s = s .. '['..k..'] = ' .. table_dump(v) .. ','
+ end
+ return s .. '} '
+ else
+ return tostring(o)
+ end
+ end ---function table_dump(0);
+
+ ---convert numeric action type to translatable string
+ ---@param action_type number
+ ---@return string
+ function action_type_to_string(action_type)
+ if action_type == ACTION_TYPE_PROJECTILE then
+ return "$inventory_actiontype_projectile";
+ end
+ if action_type == ACTION_TYPE_STATIC_PROJECTILE then
+ return "$inventory_actiontype_staticprojectile";
+ end
+ if action_type == ACTION_TYPE_MODIFIER then
+ return "$inventory_actiontype_modifier";
+ end
+ if action_type == ACTION_TYPE_DRAW_MANY then
+ return "$inventory_actiontype_drawmany";
+ end
+ if action_type == ACTION_TYPE_MATERIAL then
+ return "$inventory_actiontype_material";
+ end
+ if action_type == ACTION_TYPE_OTHER then
+ return "$inventory_actiontype_other";
+ end
+ if action_type == ACTION_TYPE_UTILITY then
+ return "$inventory_actiontype_utility";
+ end
+ if action_type == ACTION_TYPE_PASSIVE then
+ return "$inventory_actiontype_passive";
+ end
+ return "";
+ end
+
+ ---convert numeric action type to slot sprite
+ ---@param action_type number
+ ---@return string
+ function action_type_to_slot_sprite(action_type)
+ if action_type == ACTION_TYPE_DRAW_MANY then
+ return "data/ui_gfx/inventory/item_bg_draw_many.png";
+ end
+ if action_type == ACTION_TYPE_MATERIAL then
+ return "data/ui_gfx/inventory/item_bg_material.png";
+ end
+ if action_type == ACTION_TYPE_MODIFIER then
+ return "data/ui_gfx/inventory/item_bg_modifier.png";
+ end
+ if action_type == ACTION_TYPE_OTHER then
+ return "data/ui_gfx/inventory/item_bg_other.png";
+ end
+ if action_type == ACTION_TYPE_PASSIVE then
+ return "data/ui_gfx/inventory/item_bg_passive.png";
+ end
+ if action_type == ACTION_TYPE_PROJECTILE then
+ return "data/ui_gfx/inventory/item_bg_projectile.png";
+ end
+ if action_type == ACTION_TYPE_STATIC_PROJECTILE then
+ return "data/ui_gfx/inventory/item_bg_static_projectile.png";
+ end
+ if action_type == ACTION_TYPE_UTILITY then
+ return "data/ui_gfx/inventory/item_bg_utility.png";
+ end
+ return "data/ui_gfx/inventory/hover_info_empty_slot.png";
+ end
+
+ ---these are for column 3, show_condition
+ __show_always = function(_) return true; end ---always show datum
+ __show_nz = function(value) return (value~=nil and value~=0) and true or false; end --show non-zero datum
+ __show_gz = function(value) return (value~=nil and value>0) and true or false; end --show greater than zero datum
+ __show_many = function(value) return (value~=nil and value>1) and true or false; end --show daum more than one
+
+ __val = function(value) return value; end ---return value unmodified
+ __trans = function(value) return GameTextGetTranslatedOrNot(value); end ---pass value to game translation table
+ __yesno = function(value) return value and "$menu_yes" or "$menu_no"; end ---treat value as boolean, return translatable yes/no
+ __time = function(value) return GameTextGet("$inventory_seconds", string.format("%1.2f", value)); end ---format value as locale-translated time
+ __deg = function(value) return GameTextGet("$inventory_degrees", string.format("%d", value)); end ---format value as locale-translated "degrees"
+ __pct = function(value) return GameTextGet("$menu_slider_percentage", value); end ---format value as locale-translated "percentage"
+ __round = function(value) return math.sign(value) * math.floor(math.abs(value) + 0.49999999999999994); end ---round value via math shortcut, supporting float impercision
+ __type = function(value) return action_type_to_string(value); end ---return action type as string, ie "Static Projectile"
+
+ ---TODO: Learn how to properly annotate this
+ local spell_tooltip_stats_format =
+ { ---membername_pattern = { icon, gametext, show_cond(value), show_value_gametext(value), } -- expand w/ "is_extra" for "extra-data" tooltips/sorting/etc
+ name = { nil, nil, __show_always, __trans },
+ description = { nil, nil, __show_always, __trans },
+ sprite = { __val, nil, __show_always, nil },
+ type = { "data/ui_gfx/inventory/icon_action_type.png", "$inventory_actiontype", __show_always, __type },
+ draw_actions = { "data/ui_gfx/inventory/icon_gun_actions_per_round.png", "$inventory_actiontype_drawmany", __show_many, __val },
+ max_uses = { "data/ui_gfx/inventory/icon_action_max_uses.png", "$inventory_usesremaining", __show_nz, __val },
+ mana = { "data/ui_gfx/inventory/icon_mana_drain.png", "$inventory_manadrain", __show_nz, __val },
+ fire_rate_wait = { "data/ui_gfx/inventory/icon_gun_reload_time.png", "$inventory_castdelay", __show_always, __time },
+ reload_time = { "data/ui_gfx/inventory/icon_reload_time.png", "$inventory_rechargetime", __show_nz, __time },
+ damage_projectile_add = { "data/ui_gfx/inventory/icon_damage_projectile.png", "$inventory_damage", __show_nz, __round },
+ damage_basic = { "data/ui_gfx/inventory/icon_damage_projectile.png", "$inventory_mod_damage", __show_nz, __round },
+ damage_slice = { "data/ui_gfx/inventory/icon_damage_slice.png", "$inventory_dmg_slice", __show_nz, __round },
+ damage_melee_add = { "data/ui_gfx/inventory/icon_damage_melee.png", "$inventory_dmg_melee", __show_nz, __round },
+ damage_melee = { "data/ui_gfx/inventory/icon_damage_melee.png", "$inventory_dmg_melee", __show_nz, __round },
+ damage_electricity_add = { "data/ui_gfx/inventory/icon_damage_electricity.png", "$inventory_mod_damage_electric", __show_nz, __round },
+ damage_electricity = { "data/ui_gfx/inventory/icon_damage_electricity.png", "$inventory_mod_damage_electric", __show_nz, __round },
+ damage_fire_add = { "data/ui_gfx/inventory/icon_damage_fire.png", "$inventory_dmg_fire", __show_nz, __round },
+ damage_fire = { "data/ui_gfx/inventory/icon_damage_fire.png", "$inventory_dmg_fire", __show_nz, __round },
+ damage_explosion_add = { "data/ui_gfx/inventory/icon_damage_explosion.png", "$inventory_dmg_explosion", __show_nz, __round },
+ explosion_radius = { "data/ui_gfx/inventory/icon_explosion_radius.png", "$inventory_explosion_radius", __show_nz, __val },
+ damage_explosion = { "data/ui_gfx/inventory/icon_damage_explosion.png", "$inventory_dmg_explosion", __show_nz, __round },
+ damage_curse = { "data/ui_gfx/inventory/icon_damage_curse.png", "$inventory_dmg_curse", __show_nz, __round },
+ damage_ice = { "data/ui_gfx/inventory/icon_damage_ice.png" , "$inventory_dmg_ice", __show_nz, __round },
+ damage_drill = { "data/ui_gfx/inventory/icon_damage_drill.png", "$inventory_dmg_drill", __show_nz, __round },
+ damage_poison = { "data/ui_gfx/inventory/icon_damage_curse.png", "$inventory_dmg_poison", __show_nz, __round },
+ damage_healing = { "data/ui_gfx/inventory/icon_damage_healing.png", "$inventory_dmg_healing", __show_nz, __round },
+ damage_radioactive = { "data/ui_gfx/inventory/icon_damage_curse.png", "$inventory_dmg_radioactive", __show_nz, __round },
+ -- speed_multiplier = { "data/ui_gfx/inventory/icon_speed_multiplier.png", "$inventory_speed", __show_many, __val },
+ speed = { "data/ui_gfx/inventory/icon_speed_multiplier.png", "$inventory_speed", __show_nz, __round },
+ damage_critical_chance = { "data/ui_gfx/inventory/icon_damage_critical_chance.png", "$inventory_mod_critchance", __show_nz, __pct },
+ projectiles = { "data/ui_gfx/inventory/icon_gun_actions_per_round.png", "$inventory_type_projectile", __show_many, __val },
+ spread_degrees = { "data/ui_gfx/inventory/icon_spread_degrees.png", "$inventory_spread", __show_nz, __deg },
+ -- speed_min = { "data/ui_gfx/inventory/icon_speed_multiplier.png", "$inventory_speed", __show_nz, __round },
+ -- speed_max = { "data/ui_gfx/inventory/icon_speed_multiplier.png", "$inventory_speed", __show_nz, __round },
+ }
+
+ ---return formatted action structure for parsing in gui routines
+ ---@param in_action table|string action or action_id to be processed
+ ---@return table?
+ function get_action_struct(in_action)
+ if type(in_action)=="string" and actions_by_id[in_action]~=nil then in_action = actions_by_id[in_action]; end ---if passed action, switch to action_id string instead
+ if type(in_action)~="table" then return {}; end ---if still not string, exit
+
+ if actions_by_id[in_action]~=nil and actions_by_id[in_action].metadata~=nil and actions_by_id[in_action].c~=nil then return nil; end ---ensure action has not been simulated prior
+
+ get_action_metadata(in_action.id);
+
+ local struct_data = {};
+ local struct_index = 0;
+ local source_pool = {in_action, in_action.c };
+ for _, projectile_component_pool in pairs(in_action.metadata) do
+ for _, projectile_component_member_pool in pairs(projectile_component_pool) do
+ source_pool[#source_pool+1] = projectile_component_member_pool;
+ end
+ end
+
+ for _, member_pool in ipairs(source_pool) do
+ for member_name, member_value in pairs(member_pool) do
+ for datum_name, datum_formatting in pairs(spell_tooltip_stats_format) do
+ if member_name==datum_name and (datum_formatting[3](member_value)==true) then
+ local thismember_data = {}
+ if datum_formatting[1]~=nil then
+ if type(datum_formatting[1])=="function" then
+ thismember_data.icon = datum_formatting[1](member_value);
+ else
+ thismember_data.icon = datum_formatting[1];
+ thismember_data.label = GameTextGetTranslatedOrNot(datum_formatting[2]);
+ end
+ end
+ if datum_formatting[4]~=nil then
+ thismember_data.value = datum_formatting[4](member_value);
+ end
+ if thismember_data["icon"]~=nil or thismember_data["value"]~=nil then
+ struct_index = struct_index + 1;
+ thismember_data.name = member_name;
+ struct_data[struct_index] = thismember_data;
+ end
+ end
+ end
+ end
+ end
+ return struct_data;
+ end
+
+ function debug_print_action(debug_action)
+ local action_struct_pool = get_action_struct(debug_action);
+ if action_struct_pool==nil then return; end
+
+ for action_member, action_struct in pairs(action_struct_pool) do
+ print("member: " .. action_member);
+ print("- icon: " .. (action_struct.icon or " "));
+ print("- label: " .. (action_struct.label or " "));
+ print("- value: " .. (action_struct.value or " "));
+ print(" ");
+ end
+ end -- function debug_print_action();
+
+ actions_by_id__init_done = true;
+end -- if initialized
+
+---intended to be run every world update w/ minimal impact
+if action_count<#actions then
+ print("actions_by_id: capturing new actions, group " .. action_count .. " to (at most) " .. action_count + 100);
+ collect_action_data(100);
+ actions_by_id__notify_when_finished = true;
+ if action_count==#actions then actions_by_id_loaded = true; end
+elseif actions_by_id__notify_when_finished==true then
+ actions_by_id__notify_when_finished = false;
+ actions_by_id_loaded = true;
+ print("actions_by_id: scan done, storing " .. action_count .. " actions")
+ local debug_action = actions_by_id["RUBBER_BALL"];
+
+ print("table = " .. table_dump(debug_action));
+ print("action: " .. GameTextGetTranslatedOrNot(debug_action.name));
+
+ -- debug_print_action(debug_action);
+end
diff --git a/files/cost.lua b/files/cost.lua
new file mode 100644
index 0000000..0c7a4bc
--- /dev/null
+++ b/files/cost.lua
@@ -0,0 +1,102 @@
+
+
+function __cost_func_wand_type(_)
+ return 200;
+end
+
+function __cost_func_shuffle(_shuffle)
+ return _shuffle and 0 or 100;
+end
+
+function __cost_func_spells_per_cast(a_p_c)
+ return math.ceil(math.max(a_p_c-1,0)*500);
+end
+
+function __cost_func_cast_delay(_castdelay)
+ return math.ceil((0.01 ^ ((_castdelay/60) - 1.8) + 200) * 0.1);
+end
+
+function __cost_func_recharge_time(_rechargetime)
+ return math.ceil((0.01 ^ ((_rechargetime/60) - 1.8) + 200) * 0.1);
+end
+
+function __cost_func_mana_max(_manamax)
+ return math.ceil(_manamax);
+end
+
+function __cost_func_mana_charge_speed(_manachargespeed)
+ return math.ceil(_manachargespeed * 2);
+end
+
+function __cost_func_capacity(_capacity)
+ return math.ceil((math.max(_capacity - 1, 0)) * 50);
+end
+
+function __cost_func_spread(_spread)
+ return math.ceil(math.abs(10 - _spread) * 5);
+end
+
+function __cost_func_always_cast_spells(_alwayscasts)
+ local _val = 0; for _, _a_c_id in ipairs(_alwayscasts) do if (_a_c_id~=nil and actions_by_id[_a_c_id]~=nil and actions_by_id[_a_c_id].price~=nil) then _val = _val + __get_ac_raw_cost(_a_c_id); end; end; return math.ceil(_val);
+end
+
+function __cost_func_always_cast_spell(_alwayscast)
+ return math.ceil(__get_ac_raw_cost(_alwayscast));
+end
+
+function __cost_func_always_cast_count(_alwayscasts)
+ return math.ceil((_alwayscasts ^ 2) * 100);
+end
+
+function __get_ac_raw_cost(_a_c_id)
+ if _a_c_id==nil then return 0; end
+ return math.ceil(actions_by_id[_a_c_id].price * 5);
+end
+
+function fill_wand_stat_cost(wand_data)
+ wand_data.cost = {};
+ wand_data.cost.wand_type = math.ceil(__cost_func_wand_type(wand_data["wand_type"]) * mod_setting.buy_wand_price_multiplier);
+ wand_data.cost.shuffle = math.ceil(__cost_func_shuffle(wand_data["shuffle"]) * mod_setting.buy_wand_price_multiplier);
+ wand_data.cost.spells_per_cast = math.ceil(__cost_func_spells_per_cast(wand_data["spells_per_cast"]) * mod_setting.buy_wand_price_multiplier);
+ wand_data.cost.cast_delay = math.ceil(__cost_func_cast_delay(wand_data["cast_delay"]) * mod_setting.buy_wand_price_multiplier);
+ wand_data.cost.recharge_time = math.ceil(__cost_func_recharge_time(wand_data["recharge_time"]) * mod_setting.buy_wand_price_multiplier);
+ wand_data.cost.mana_max = math.ceil(__cost_func_mana_max(wand_data["mana_max"]) * mod_setting.buy_wand_price_multiplier);
+ wand_data.cost.mana_charge_speed = math.ceil(__cost_func_mana_charge_speed(wand_data["mana_charge_speed"]) * mod_setting.buy_wand_price_multiplier);
+ wand_data.cost.capacity = math.ceil(__cost_func_capacity(wand_data["capacity"]) * mod_setting.buy_wand_price_multiplier);
+ wand_data.cost.spread = math.ceil(__cost_func_spread(wand_data["spread"]) * mod_setting.buy_wand_price_multiplier);
+ wand_data.cost.always_cast_count = math.ceil(__cost_func_always_cast_count(wand_data["always_cast_count"]) * mod_setting.buy_wand_price_multiplier);
+ local _sum = 0;
+ for _, _cost in pairs(wand_data["cost"]) do
+ _sum = _sum + _cost;
+ end
+ wand_data.cost.stat_sum = _sum;
+end
+
+function get_wand_buy_price(wand_data)
+ local price = 0;
+ fill_wand_stat_cost(wand_data);
+ price = wand_data.cost.stat_sum;
+ price = price + math.ceil(__cost_func_always_cast_spells(wand_data["always_cast_spells"]) * mod_setting.buy_spell_price_multiplier);
+
+ return math.ceil(price);
+end
+
+function get_spell_purchase_price(action_id)
+ if action_id==nil or type(action_id)~="string" then return nil; end
+
+ return math.ceil(actions_by_id[action_id].price * mod_setting.buy_spell_price_multiplier);
+end
+
+function get_spell_research_price(action_id)
+ if action_id==nil or type(action_id)~="string" then return nil; end
+
+ return math.ceil(actions_by_id[action_id].price * mod_setting.research_spell_price_multiplier);
+end
+
+function get_spell_entity_research_price(entity_id)
+ local action_id = get_spell_entity_action_id(entity_id);
+ if action_id == nil then
+ return nil;
+ end
+ return get_spell_research_price(action_id);
+end
diff --git a/files/data_store.lua b/files/data_store.lua
index f895877..9c313ee 100644
--- a/files/data_store.lua
+++ b/files/data_store.lua
@@ -1,840 +1,925 @@
-dofile_once("mods/persistence/config.lua");
-dofile_once("data/scripts/gun/gun_actions.lua");
-dofile_once("data/scripts/gun/procedural/wands.lua");
-dofile_once("mods/persistence/files/wand_spell_helper.lua");
-
-spells_per_cast_min = 0;
-mana_max_min = 0;
-mana_charge_speed_min = 0;
-capacity_min = 0;
-
-local data_store = {};
-local flag_prefix = "persistence";
-local selected_save_id;
-
-function get_save_count()
- return 5;
-end
-
-function get_template_count()
- return 5;
-end
-
-local function number_to_hex(number)
- if number == nil then
- return nil;
- end
- local positive = math.abs(number);
- return (positive == number and "" or "-") .. string.format("%x", positive);
-end
-
-local function hex_to_number(hex)
- if hex == nil then
- return nil;
- end
- if string.sub(hex, 1, 1) == "-" then
- return tonumber(string.sub(hex, 2), 16) * -1;
- else
- return tonumber(hex, 16);
- end
-end
-
-local hex_chars = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "-" }
-local function save_hex(name, hex)
- if hex == nil then
- for j = 1, #hex_chars do
- RemoveFlagPersistent(flag_prefix .. "_" .. name .. "_" .. 1 .. "_" .. hex_chars[j]);
- end
- return;
- end
- for i = 1, #hex do
- for j = 1, #hex_chars do
- RemoveFlagPersistent(flag_prefix .. "_" .. name .. "_" .. i .. "_" .. hex_chars[j]);
- end
- AddFlagPersistent(flag_prefix .. "_" .. name .. "_" .. i .. "_" .. string.sub(hex, i, i));
- end
- for j = 1, #hex_chars do
- RemoveFlagPersistent(flag_prefix .. "_" .. name .. "_" .. #hex + 1 .. "_" .. hex_chars[j]);
- end
-end
-
-local function load_hex(name)
- local output = "";
- local i = 1;
- repeat
- local hex_found = false;
- for j = 1, #hex_chars do
- if HasFlagPersistent(flag_prefix .. "_" .. name .. "_" .. i .. "_" .. hex_chars[j]) then
- output = output .. hex_chars[j];
- hex_found = true;
- break;
- end
- end
- i = i + 1;
- until not hex_found
- return (output == "" and nil or output);
-end
-
-function load_save_ids()
- for i = 1, get_save_count() do
- if HasFlagPersistent(flag_prefix .. "_" .. tostring(i)) then
- if data_store[i] == nil then
- data_store[i] = {};
- end
- end
- end
- return get_save_ids();
-end
-
-function set_run_created_with_mod()
- GameAddFlagRun(flag_prefix .. "_using_mod");
-end
-
-function get_run_created_with_mod()
- return GameHasFlagRun(flag_prefix .. "_using_mod");
-end
-
-function get_save_ids()
- local output = {};
- for i = 1, get_save_count() do
- if data_store[i] ~= nil then
- output[i] = true;
- end
- end
- return output;
-end
-
-function get_selected_save_id()
- if selected_save_id == nil then
- for i = 0, get_save_count() do
- if GameHasFlagRun(flag_prefix .. "_selected_save_" .. tostring(i)) then
- selected_save_id = i;
- return i;
- end
- end
- return nil;
- else
- return selected_save_id;
- end
-end
-
-function set_selected_save_id(id)
- for i = 0, get_save_count() do
- GameRemoveFlagRun(flag_prefix .. "_selected_save_" .. tostring(i));
- end
- GameAddFlagRun(flag_prefix .. "_selected_save_" .. tostring(id));
-end
-
-function create_new_save(save_id)
- delete_save(save_id);
- load(save_id);
- AddFlagPersistent(flag_prefix .. "_" .. tostring(save_id));
-end
-
-function delete_save(save_id)
- local save_id_string = tostring(save_id);
-
- save_hex(save_id_string .. "_spells_per_cast", nil);
- save_hex(save_id_string .. "_cast_delay_min", nil);
- save_hex(save_id_string .. "_cast_delay_max", nil);
- save_hex(save_id_string .. "_recharge_time_min", nil);
- save_hex(save_id_string .. "_recharge_time_max", nil);
- save_hex(save_id_string .. "_mana_max", nil);
- save_hex(save_id_string .. "_capacity", nil);
- save_hex(save_id_string .. "_spread_min", nil);
- save_hex(save_id_string .. "_spread_max", nil);
- save_hex(save_id_string .. "_money", nil);
- for i = 1, #actions do
- RemoveFlagPersistent(flag_prefix .. "_" .. save_id_string .. "_spell_" .. string.lower(actions[i].id));
- RemoveFlagPersistent(flag_prefix .. "_" .. save_id_string .. "_always_cast_spell_" .. string.lower(actions[i].id));
- end
- for i = 1, #wands do
- RemoveFlagPersistent(flag_prefix .. "_" .. save_id_string .. "_wand_type_" .. sprite_file_to_wand_type(wands[i].file));
- end
-
- for i = 1, get_template_count() do
- delete_template(save_id, i);
- end
-
- RemoveFlagPersistent(flag_prefix .. "_" .. save_id_string);
- data_store[save_id] = nil;
-end
-
-function get_player_money()
- local money = tonumber(ComponentGetValue(get_wallet(), "money"));
- return money == nil and 0 or money;
-end
-
-function set_player_money(value)
- ComponentSetValue(get_wallet(), "money", value);
-end
-
-function load(save_id)
- local save_id_string = tostring(save_id);
-
- data_store[save_id] = {};
- data_store[save_id]["spells_per_cast"] = hex_to_number(load_hex(save_id_string .. "_spells_per_cast"));
- data_store[save_id]["cast_delay_min"] = hex_to_number(load_hex(save_id_string .. "_cast_delay_min"));
- data_store[save_id]["cast_delay_max"] = hex_to_number(load_hex(save_id_string .. "_cast_delay_max"));
- data_store[save_id]["recharge_time_min"] = hex_to_number(load_hex(save_id_string .. "_recharge_time_min"));
- data_store[save_id]["recharge_time_max"] = hex_to_number(load_hex(save_id_string .. "_recharge_time_max"));
- data_store[save_id]["mana_max"] = hex_to_number(load_hex(save_id_string .. "_mana_max"));
- data_store[save_id]["mana_charge_speed"] = hex_to_number(load_hex(save_id_string .. "_mana_charge_speed"));
- data_store[save_id]["capacity"] = hex_to_number(load_hex(save_id_string .. "_capacity"));
- local spread_min = hex_to_number(load_hex(save_id_string .. "_spread_min"));
- if spread_min ~= nil then
- spread_min = spread_min / 10;
- end
- data_store[save_id]["spread_min"] = spread_min;
- local spread_max = hex_to_number(load_hex(save_id_string .. "_spread_max"));
- if spread_max ~= nil then
- spread_max = spread_max / 10;
- end
- data_store[save_id]["spread_max"] = spread_max;
- data_store[save_id]["money"] = hex_to_number(load_hex(save_id_string .. "_money"));
-
- data_store[save_id]["spells"] = {};
- data_store[save_id]["always_cast_spells"] = {};
- for i = 1, #actions do
- if HasFlagPersistent(flag_prefix .. "_" .. save_id_string .. "_spell_" .. string.lower(actions[i].id)) then
- data_store[save_id]["spells"][actions[i].id] = true;
- end
- if HasFlagPersistent(flag_prefix .. "_" .. save_id_string .. "_always_cast_spell_" .. string.lower(actions[i].id)) then
- data_store[save_id]["always_cast_spells"][actions[i].id] = true;
- end
- end
-
- data_store[save_id]["wand_types"] = {};
- for i = 1, #mod_config.default_wands do
- data_store[save_id]["wand_types"]["default_" .. tostring(i)] = true;
- end
- for i = 1, #wands do
- local wand_type = sprite_file_to_wand_type(wands[i].file);
- if HasFlagPersistent(flag_prefix .. "_" .. save_id_string .. "_wand_type_" .. string.lower(wand_type)) then
- data_store[save_id]["wand_types"][wand_type] = true;
- end
- end
-
- data_store[save_id]["templates"] = {};
- for i = 1, get_template_count() do
- if HasFlagPersistent(flag_prefix .. "_" .. save_id_string .. "_template_" .. tostring(i)) then
- data_store[save_id]["templates"][i] = {};
- if HasFlagPersistent(flag_prefix .. "_" .. save_id_string .. "_template_" .. tostring(i) .. "_shuffle") then
- data_store[save_id]["templates"][i]["shuffle"] = true;
- else
- data_store[save_id]["templates"][i]["shuffle"] = false;
+if persistence_data_store_loaded~=true then
+ -- On load only
+ wands=wands or {};
+
+ dofile_once(mod_dir .. "files/helper.lua");
+ dofile_once(mod_dir .. "files/encoder.lua")
+
+ local data_store = {};
+
+ ---@type integer
+ default_profile_id = tonumber(mod_setting.always_choose_save_id) or 0;
+ ---@type integer
+ selected_profile_id = 0;
+ ---@type integer
+ DISABLE_PROFILE_ID = -999;
+ ---@type integer
+ loaded_profile_id = 0;
+
+ function get_profile_count() return 4; end
+ function get_template_count() return 5; end
+
+
+ ---- =================
+ ---- PRIVATE FUNCTIONS
+ ---- =================
+
+
+ local function _do_startup_paycheck_check()
+ if not GameHasFlagRun("persistence_startup_paid") then
+ GameAddFlagRun("persistence_startup_paid");
+ if mod_setting.start_with_money>0 then
+ ---@diagnostic disable-next-line: param-type-mismatch
+ local _withdraw = math.min(mod_setting.start_with_money, get_stash_money());
+ GamePrint(string.format("Persistence: Run start, $ %i from Stash", _withdraw));
+ transfer_money_stash_to_player(_withdraw);
+ end
+ end
+ end
+
+ local function _do_holy_mountain_paycheck_check()
+ if mod_setting.holy_mountain_money==0 and mod_setting.holy_mountain_reward==0 then return; end
+
+ local _workshop_e_id = tonumber(GlobalsGetValue("workshop_e_id", "0")) or 0;
+ if not EntityHasTag(_workshop_e_id, "persistence_visited") then return; end
+ if not EntityHasTag(_workshop_e_id, "persistence_unpaid") then return; end
+ if EntityHasTag(_workshop_e_id, "persistence_paid") then return; end
+
+ ---@diagnostic disable-next-line: param-type-mismatch
+ local _withdraw = math.min(get_stash_money(), mod_setting.holy_mountain_money);
+ if _withdraw > 0 then
+ GamePrint(string.format("Persistence: Holy Mountain paycheck, $ %i from Stash", _withdraw));
+ transfer_money_stash_to_player(_withdraw);
+ end
+
+ local _reward = mod_setting.holy_mountain_reward;
+ if _reward > 0 then
+ GamePrint(string.format("Persistence: Holy Mountain paycheck, $ %i", _reward));
+ increment_player_money(_reward);
+ end
+
+ local _ent_x, _ent_y = EntityGetTransform(_workshop_e_id);
+ local _workshops_here = EntityGetInRadiusWithTag(_ent_x, _ent_y, 500, "persistence_workshop");
+ for _, _test_e_id in ipairs(_workshops_here) do
+ EntityRemoveTag(_test_e_id, "persistence_unpaid");
+ EntityAddTag(_test_e_id, "persistence_paid");
+ EntityAddTag(_test_e_id, "persistence_visited");
+ end
+ end
+
+
+ ---return wand bounds for profile
+ ---@param profile_id integer
+ ---@return wand_bounds_data
+ local function _get_wand_bounds(profile_id)
+ return {
+ wand_types = data_store[profile_id]["wand_types"],
+ always_casts = data_store[profile_id]["always_cast_spells"],
+ always_cast_count = data_store[profile_id]["always_cast_count"],
+ spells_per_cast = { 1,
+ data_store[profile_id]["spells_per_cast"] or 1 },
+ mana_max = { 1,
+ data_store[profile_id]["mana_max"] or 50},
+ mana_charge_speed = { 1,
+ data_store[profile_id]["mana_charge_speed"] or 10},
+ capacity = { 1,
+ data_store[profile_id]["capacity"] or 1},
+ cast_delay = { data_store[profile_id]["cast_delay_min"]==nil and 15 or data_store[profile_id]["cast_delay_min"],
+ data_store[profile_id]["cast_delay_max"]==nil and 15 or data_store[profile_id]["cast_delay_max"]},
+ recharge_time = { data_store[profile_id]["recharge_time_min"]==nil and 15 or data_store[profile_id]["recharge_time_min"],
+ data_store[profile_id]["recharge_time_max"]==nil and 15 or data_store[profile_id]["recharge_time_max"]},
+ spread = { data_store[profile_id]["spread_min"]==nil and 5 or data_store[profile_id]["spread_min"],
+ data_store[profile_id]["spread_max"]==nil and 5 or data_store[profile_id]["spread_max"]}
+ };
+ end
+
+ ---return in-memory {spell_id = true, ... }, spells_known for profile
+ ---@param profile_id integer
+ ---@return table spells {spell_id = true, ...}
+ ---@return integer count known spells
+ local function _get_profile_spells(profile_id)
+ return data_store[profile_id]["spells"], data_store[profile_id]["spells_known"]~=nil and data_store[profile_id]["spells_known"] or 0;
+ end
+
+ ---update quantity of known spells, saved to datastore and disk
+ ---@param profile_id integer
+ ---@return integer count known spells
+ local function _update_spells_known_count(profile_id)
+ local spells_known = 0;
+ for _, known in pairs(data_store[profile_id]["spells"]) do
+ if known then spells_known = spells_known + 1; end
+ end
+ data_store[profile_id]["spells_known"] = spells_known;
+ encoder_write_integer(profile_id .. "_spells_known", spells_known);
+ return spells_known;
+ end
+
+ ---add spells and update known spells count, saved to datastore and disk
+ ---@param profile_id integer
+ ---@param spells table {1=spell_id, 2=spell_id, ... }
+ local function _add_spells(profile_id, spells)
+ if spells == nil or #spells == 0 then return; end
+ for _, spell_id in ipairs(spells) do
+ data_store[profile_id]["spells"][spell_id] = true;
+ encoder_add_flag(profile_id .. "_spell_" .. string.lower(spell_id));
+ end
+ _update_spells_known_count(profile_id);
+ end
+
+ ---return in-memory {spell_id = true, ... }, always_cast_spells_known for profile
+ ---@param profile_id integer
+ ---@return table spells {spell_id = true, ...}
+ ---@return integer count known always_cast spells
+ local function _get_always_cast_spells(profile_id)
+ return data_store[profile_id]["always_cast_spells"], data_store[profile_id]["always_cast_spells_known"] or 0;
+ end
+
+ ---update quantity of known always_cast spells, saved to datastore and disk
+ ---@param profile_id integer
+ ---@return integer count known always_cast spells
+ local function _update_always_cast_spells_known_count(profile_id)
+ local always_casts_known = 0;
+ for _, known in pairs(data_store[profile_id]["always_cast_spells"]) do
+ if known then always_casts_known = always_casts_known + 1; end
+ end
+ data_store[profile_id]["always_cast_spells_known"]=always_casts_known;
+ encoder_write_integer(profile_id .. "_always_cast_spells_known", always_casts_known);
+ return always_casts_known;
+ end
+
+ ---add always_cast spells and update known always_cast spells count, saved to datastore and disk
+ ---@param profile_id integer
+ ---@param ac_spells table {1=spell_id, 2=spell_id, ... }
+ local function _add_always_cast_spells(profile_id, ac_spells)
+ if ac_spells == nil or #ac_spells == 0 then return; end
+ for _, curr_ac_spell_id in ipairs(ac_spells) do
+ data_store[profile_id]["always_cast_spells"][curr_ac_spell_id] = true;
+ encoder_add_flag(profile_id .. "_always_cast_spell_" .. string.lower(curr_ac_spell_id));
+ end
+ _update_always_cast_spells_known_count(profile_id);
+ end
+
+ ---load spells, always_cast spells, and known quantities from disk
+ ---@param profile_id integer
+ local function _load_profile_spells(profile_id)
+ if data_store ~= nil and data_store[profile_id] ~= nil then
+ local prefixed_string = profile_id;
+ data_store[profile_id]["always_cast_spells"] = {};
+ data_store[profile_id]["spells"] = {};
+ for curr_action_id, _ in pairs(actions_by_id) do
+ if encoder_has_flag(prefixed_string .. "_spell_" .. string.lower(curr_action_id)) then data_store[profile_id]["spells"][curr_action_id] = true; end
+ if encoder_has_flag(prefixed_string .. "_always_cast_spell_" .. string.lower(curr_action_id)) then data_store[profile_id]["always_cast_spells"][curr_action_id] = true; end
+ end
+ data_store[profile_id]["spells_known"] = _update_spells_known_count(profile_id);
+ data_store[profile_id]["always_cast_spells_known"] = _update_always_cast_spells_known_count(profile_id);
+ end
+ end
+
+ ---update quantity of known wand types, saved to datastore and disk
+ ---@param profile_id integer
+ ---@return integer count known wand types
+ local function _update_wand_types_known_count(profile_id)
+ local wand_types_known = 0;
+ for _, known in pairs(data_store[profile_id]["wand_types"]) do
+ if known then wand_types_known = wand_types_known + 1; end
+ end
+ data_store[profile_id]["wand_types_known"]=wand_types_known;
+ encoder_write_integer(profile_id .. "_wand_types_known", wand_types_known);
+ return wand_types_known;
+ end
+
+ ---add wand types and update known wand types count, saved to datastore and disk
+ ---@param profile_id integer
+ ---@param wand_types table {1=wand_type, 2=wand_type, ... }
+ local function _add_wand_types(profile_id, wand_types)
+ for _, curr_wand_type in ipairs(wand_types) do
+ if string.sub(curr_wand_type, 1, #"default") ~= "default" then
+ data_store[profile_id]["wand_types"][curr_wand_type] = true;
+ encoder_add_flag(profile_id .. "_wand_type_" .. string.lower(curr_wand_type));
+ end
+ end
+ _update_wand_types_known_count(profile_id);
+ end
+
+ ---load wand types and known quantity from disk
+ ---@param profile_id integer
+ local function _load_wand_types(profile_id)
+ local idx = 0;
+ data_store[profile_id]["wand_types"] = {};
+ data_store[profile_id]["wand_types_idx"] = {};
+ ---TODO: is this actually necessary? Debug and check
+ local prefixed_string = profile_id;
+ for i, _ in ipairs(default_wands) do
+ idx = idx + 1;
+ data_store[profile_id]["wand_types"]["default_" .. i] = true;
+ data_store[profile_id]["wand_types_idx"][idx] = "default_" .. i;
+ end
+ for _, scan_wand in ipairs(wands) do
+ local scan_wand_type = sprite_file_to_wand_type(scan_wand.file);
+ if encoder_has_flag(prefixed_string .. "_wand_type_" .. string.lower(scan_wand_type)) then
+ idx = idx + 1;
+ data_store[profile_id]["wand_types"][scan_wand_type] = true;
+ data_store[profile_id]["wand_types_idx"][idx] = scan_wand_type;
+ end
+ end
+ data_store[profile_id]["wand_types_known"] = _update_wand_types_known_count(profile_id);
+ end
+
+ local function _set_spells_per_cast(profile_id, value)
+ data_store[profile_id]["spells_per_cast"] = value;
+ encoder_write_integer(profile_id .. "_spells_per_cast", data_store[profile_id]["spells_per_cast"]);
+ end
+
+ local function _set_cast_delay_min(profile_id, value)
+ data_store[profile_id]["cast_delay_min"] = value;
+ encoder_write_integer(profile_id .. "_cast_delay_min", data_store[profile_id]["cast_delay_min"]);
+ end
+
+ local function _set_cast_delay_max(profile_id, value)
+ data_store[profile_id]["cast_delay_max"] = value;
+ encoder_write_integer(profile_id .. "_cast_delay_max", data_store[profile_id]["cast_delay_max"]);
+ end
+
+ local function _set_recharge_time_min(profile_id, value)
+ data_store[profile_id]["recharge_time_min"] = value;
+ encoder_write_integer(profile_id .. "_recharge_time_min", data_store[profile_id]["recharge_time_min"]);
+ end
+
+ local function _set_recharge_time_max(profile_id, value)
+ data_store[profile_id]["recharge_time_max"] = value;
+ encoder_write_integer(profile_id .. "_recharge_time_max", data_store[profile_id]["recharge_time_max"]);
+ end
+
+ local function _set_mana_max(profile_id, value)
+ data_store[profile_id]["mana_max"] = value;
+ encoder_write_integer(profile_id .. "_mana_max", data_store[profile_id]["mana_max"]);
+ end
+
+ local function _set_mana_charge_speed(profile_id, value)
+ data_store[profile_id]["mana_charge_speed"] = value;
+ encoder_write_integer(profile_id .. "_mana_charge_speed", data_store[profile_id]["mana_charge_speed"]);
+ end
+
+ local function _set_capacity(profile_id, value)
+ data_store[profile_id]["capacity"] = value;
+ encoder_write_integer(profile_id .. "_capacity", data_store[profile_id]["capacity"]);
+ end
+
+ local function _set_spread_min(profile_id, value)
+ data_store[profile_id]["spread_min"] = value;
+ encoder_write_integer(profile_id .. "_spread_min", data_store[profile_id]["spread_min"] == nil and nil or math.floor(data_store[profile_id]["spread_min"] * 10));
+ end
+
+ local function _set_spread_max(profile_id, value)
+ data_store[profile_id]["spread_max"] = value;
+ encoder_write_integer(profile_id .. "_spread_max", data_store[profile_id]["spread_max"] == nil and nil or math.ceil(data_store[profile_id]["spread_max"] * 10));
+ end
+
+ -- money
+ local function _get_stash_money(profile_id)
+ return data_store[profile_id]["money"] or 0;
+ end
+
+ local function _set_stash_money(profile_id, value)
+ data_store[profile_id]["money"] = value;
+ encoder_write_integer(profile_id .. "_money", data_store[profile_id]["money"]);
+ end
+
+ ---increments stash money, returns success
+ ---@param profile_id integer profile
+ ---@param amount integer money
+ ---@return boolean success
+ local function _increment_stash_money(profile_id, amount)
+ local _stash = data_store[profile_id]["money"] or 0;
+ local _target = _stash + amount;
+ if _stash > _target then return false; end
+
+ data_store[profile_id]["money"] = _target;
+ encoder_write_integer(profile_id .. "_money", data_store[profile_id]["money"]);
+ return true;
+ end
+
+ ---decrements stash money, returns success
+ ---@param profile_id integer profile
+ ---@param amount integer money
+ ---@return boolean success
+ local function _decrement_stash_money(profile_id, amount)
+ local _stash = data_store[profile_id]["money"] or 0;
+ local _target = _stash - amount;
+ if _stash < _target then return false; end
+
+ data_store[profile_id]["money"] = _target;
+ encoder_write_integer(profile_id .. "_money", data_store[profile_id]["money"]);
+ return true;
+ end
+
+ -- always_cast_count
+ local function _get_always_cast_count(profile_id)
+ return data_store[profile_id]["always_cast_count"] or 0;
+ end
+
+ local function _set_always_cast_count(profile_id, value)
+ data_store[profile_id]["always_cast_count"] = value;
+ encoder_write_integer(tostring(profile_id) .. "_always_cast_count", data_store[profile_id]["always_cast_count"]);
+ end
+
+
+ local function _transfer_money_player_to_stash(profile_id, amount)
+ if get_player_money() < amount then
+ return false;
+ end
+
+ if _increment_stash_money(profile_id, amount) then
+ decrement_player_money(amount);
+ return true;
+ end
+ return false;
+ end
+
+ local function _transfer_money_stash_to_player(profile_id, amount)
+ local _money = get_player_money();
+ if _money >= (2^29) then return false; end
+ if _money + amount > (2^29) then return false; end
+ if _get_stash_money(profile_id) < amount then return false; end
+
+ local _newtarget = math.min(_money + amount, (2^29));
+ local _difference = _newtarget - _money;
+
+ if set_player_money( _newtarget ) then
+ return _decrement_stash_money(profile_id, _difference);
+ else
+ return false;
+ end
+ end
+
+ ---Load a profile quick-view, return values and populate datastore
+ ---@param profile_id integer profile id
+ ---@return integer|nil money stashed money
+ ---@return integer|nil always_cast quantity known always cast spells
+ ---@return integer|nil spells quantity known spells
+ ---@return integer|nil wand_types quantity known wand types
+ local function _load_profile_quick(profile_id)
+ local money = encoder_load_integer(profile_id .. "_money") or 0;
+ local always_cast = encoder_load_integer(profile_id .. "_always_cast_spells_known") or 0;
+ local spells = encoder_load_integer(profile_id .. "_spells_known") or 0;
+ local wand_types = encoder_load_integer(profile_id .. "_wand_types_known") or 0;
+
+ if always_cast==0 or spells==0 or wand_types==0 then
+ _load_profile_spells(profile_id);
+ _load_wand_types(profile_id);
+ always_cast = _update_always_cast_spells_known_count(profile_id) or 0;
+ spells = _update_spells_known_count(profile_id) or 0;
+ wand_types = _update_wand_types_known_count(profile_id) or 0;
+ end
+ data_store[profile_id]["quickloaded"] = true;
+ data_store[profile_id]["money"] = money;
+ data_store[profile_id]["always_cast_spells_known"] = always_cast;
+ data_store[profile_id]["spells_known"] = spells;
+ data_store[profile_id]["wand_types_known"] = wand_types;
+ return money, always_cast, spells, wand_types;
+ end
+
+ -- templates
+ local function _load_template(profile_id, template_id)
+ local template_id_string = template_id;
+
+ local _template = {};
+ if encoder_has_flag(profile_id .. "_template_" .. template_id_string) then
+ if encoder_has_flag(profile_id .. "_template_" .. template_id_string .. "_shuffle") then
+ _template["shuffle"] = true;
+ else
+ _template["shuffle"] = false;
+ end
+ _template["spells_per_cast"] = encoder_load_integer(profile_id .. "_template_" .. template_id_string .. "_spells_per_cast");
+ _template["cast_delay"] = encoder_load_integer(profile_id .. "_template_" .. template_id_string .. "_cast_delay");
+ _template["recharge_time"] = encoder_load_integer(profile_id .. "_template_" .. template_id_string .. "_recharge_time");
+ _template["mana_max"] = encoder_load_integer(profile_id .. "_template_" .. template_id_string .. "_mana_max");
+ _template["mana_charge_speed"] = encoder_load_integer(profile_id .. "_template_" .. template_id_string .. "_mana_charge_speed");
+ _template["capacity"] = encoder_load_integer(profile_id .. "_template_" .. template_id_string .. "_capacity");
+ _template["spread"] = encoder_load_integer(profile_id .. "_template_" .. template_id_string .. "_spread") / 10;
+
+ _template["always_cast_spells"] = {};
+ for key, _ in pairs(data_store[profile_id]["always_cast_spells"]) do
+ if encoder_has_flag(profile_id .. "_template_" .. template_id_string .. "_always_cast_spell_" .. string.lower(key)) then
+ table.insert(_template["always_cast_spells"], key);
+ end
+ end
+ _template["always_cast_count"] = #_template["always_cast_spells"];
+
+ for key, _ in pairs(data_store[profile_id]["wand_types"]) do
+ if encoder_has_flag(profile_id .. "_template_" .. template_id_string .. "_wand_type_" .. string.lower(key)) then
+ _template["wand_type"] = key;
+ break;
+ end
+ end
+ _template.price = get_wand_buy_price(_template);
+ end
+ return _template;
+ end
+
+ local function _load_templates(profile_id)
+ local _templates = {};
+ for _idx = 1, get_template_count() do
+ table.insert(_templates, _load_template(profile_id, _idx));
+ end
+ data_store[profile_id]["template"] = _templates;
+ end
+
+ local function _get_template(profile_id, template_id)
+ return data_store[profile_id]["template"][template_id];
+ end
+
+ local function _get_templates(profile_id)
+ return data_store[profile_id]["template"];
+ end
+
+ local function _delete_template(profile_id, template_id)
+ local template_prefix = profile_id .. "_template_" .. template_id;
+ encoder_clear_flag(template_prefix .. "_shuffle");
+ encoder_clear_integer(template_prefix .. "_spells_per_cast");
+ encoder_clear_integer(template_prefix .. "_cast_delay");
+ encoder_clear_integer(template_prefix .. "_recharge_time");
+ encoder_clear_integer(template_prefix .. "_mana_max");
+ encoder_clear_integer(template_prefix .. "_mana_charge_speed");
+ encoder_clear_integer(template_prefix .. "_capacity");
+ encoder_clear_integer(template_prefix .. "_spread");
+
+ for scan_action_id, _ in pairs(actions_by_id) do
+ encoder_clear_flag(template_prefix .. "_always_cast_spell_" .. string.lower(scan_action_id));
+ end
+
+ for def_idx, _ in ipairs(default_wands) do
+ encoder_clear_flag(template_prefix .. "_wand_type_default_" .. def_idx);
+ end
+ for _, scan_wand in ipairs(wands) do
+ encoder_clear_flag(template_prefix .. "_wand_type_" .. string.lower(sprite_file_to_wand_type(scan_wand.file)));
+ end
+
+ encoder_clear_flag(template_prefix);
+
+ if type(data_store[profile_id]) == "table" then
+ if type(data_store[profile_id]["template"]) == "table" then
+ data_store[profile_id]["template"][template_id] = {};
+ else
+ data_store[profile_id]["template"] = {};
+ end
+ end
+ end
+
+ local function _set_template(profile_id, template_id, wand_data)
+ _delete_template(profile_id, template_id);
+ if wand_data == nil then
+ return;
+ end
+ local template_prefix = profile_id .. "_template_" .. template_id;
+ if wand_data["shuffle"] then
+ encoder_add_flag(template_prefix .. "_shuffle");
+ end
+ encoder_write_integer(template_prefix .. "_spells_per_cast", wand_data["spells_per_cast"]);
+ encoder_write_integer(template_prefix .. "_cast_delay", wand_data["cast_delay"]);
+ encoder_write_integer(template_prefix .. "_recharge_time", wand_data["recharge_time"]);
+ encoder_write_integer(template_prefix .. "_mana_max", wand_data["mana_max"]);
+ encoder_write_integer(template_prefix .. "_mana_charge_speed", wand_data["mana_charge_speed"]);
+ encoder_write_integer(template_prefix .. "_capacity", wand_data["capacity"]);
+ encoder_write_integer(template_prefix .. "_spread", math.floor(wand_data["spread"] * 10 + 0.5));
+
+ for _, spell in ipairs(wand_data["always_cast_spells"]) do
+ encoder_add_flag(template_prefix .. "_always_cast_spell_" .. string.lower(spell));
+ end
+
+ encoder_add_flag(template_prefix .. "_wand_type_" .. string.lower(wand_data["wand_type"]));
+
+ encoder_add_flag(template_prefix);
+
+ data_store[profile_id]["template"][template_id] = wand_data or {};
+ end
+
+ --- Check if and how a wand entity is new research for profile
+ ---@param profile_id integer
+ ---@param entity_id integer
+ ---@return table research_flags {is_new, improves_spells_per_cast, improves_cast_delay_min, improves_cast_delay_max, improves_recharge_time_min, improves_recharge_time_max, improves_mana_max, improves_mana_charge_speed, improves_capacity, improves_spread_min, improves_spread_max, improves_always_cast_spells, improves_wand_types, count_new_always_cast_spells}
+ ---@return table cost_data {_sum, wand_type, always_casts, always_cast_count, shuffle, spells_per_cast, cast_delay, recharge_time, mana_max, mana_charge_speed, capacity, spread }
+ ---@return table wand_data wand data
+ local function _get_wand_entity_research(profile_id, entity_id)
+ local _research = { is_new = false,
+ b_new_is_only_type = false,
+ b_spells_per_cast = false,
+ b_cast_delay = false,
+ b_cast_delay_min = false,
+ b_cast_delay_max = false,
+ b_recharge_time = false,
+ b_recharge_time_min = false,
+ b_recharge_time_max = false,
+ b_mana_max = false,
+ b_mana_charge_speed = false,
+ b_capacity = false,
+ b_spread = false,
+ b_spread_min = false,
+ b_spread_max = false,
+ b_always_cast_spells = false,
+ i_always_cast_spells = 0,
+ b_spells = false,
+ i_spells = 0,
+ b_always_cast_count = false,
+ b_wand_types = false };
+ local _cost = { wand_type = 0,
+ always_cast_spells = 0,
+ always_cast_count = 0,
+ shuffle = 0,
+ spells_per_cast = 0,
+ cast_delay = 0,
+ recharge_time = 0,
+ mana_max = 0,
+ mana_charge_speed = 0,
+ capacity = 0,
+ spread = 0 };
+ local _profile_known_spells = _get_profile_spells(profile_id);
+ local _in_wand_data = {};
+
+ if entity_id~=nil and entity_id~=0 then
+ local _wand_bounds = _get_wand_bounds(profile_id);
+ _in_wand_data = read_wand_entity(entity_id);
+
+ if _in_wand_data["spells_per_cast"] > _wand_bounds.spells_per_cast[2] then
+ _research.b_spells_per_cast = true;
+ _research.is_new = true;
+ _cost.spells_per_cast = math.ceil(__cost_func_spells_per_cast(_in_wand_data["spells_per_cast"]));
+ end
+ if _wand_bounds.cast_delay[1] == nil or _wand_bounds.cast_delay[2] == nil then
+ _research.b_cast_delay = true;
+ _research.b_cast_delay_min = true;
+ _research.b_cast_delay_max = true;
+ _research.is_new = true;
+ _cost.cast_delay = math.ceil(__cost_func_cast_delay(_in_wand_data["cast_delay"]));
+ else
+ if _in_wand_data["cast_delay"] < _wand_bounds.cast_delay[1] then
+ _research.b_cast_delay = true;
+ _research.b_cast_delay_min = true;
+ _research.is_new = true;
+ _cost.cast_delay = math.ceil(__cost_func_cast_delay(_in_wand_data["cast_delay"]));
+ elseif _in_wand_data["cast_delay"] > _wand_bounds.cast_delay[2] then
+ _research.b_cast_delay = true;
+ _research.b_cast_delay_max = true;
+ _research.is_new = true;
+ _cost.cast_delay = math.ceil(__cost_func_cast_delay(_in_wand_data["cast_delay"]));
+ end
+ end
+ if _wand_bounds.recharge_time[1] == nil or _wand_bounds.recharge_time[2] == nil then
+ _research.b_recharge_time = true;
+ _research.b_recharge_time_min = true;
+ _research.b_recharge_time_max = true;
+ _research.is_new = true;
+ _cost.recharge_time = math.ceil(__cost_func_recharge_time(_in_wand_data["recharge_time"]));
+ else
+ if _in_wand_data["recharge_time"] < _wand_bounds.recharge_time[1] then
+ _research.b_recharge_time = true;
+ _research.b_recharge_time_min = true;
+ _research.is_new = true;
+ _cost.recharge_time = math.ceil(__cost_func_recharge_time(_in_wand_data["recharge_time"]));
+ end
+ if _in_wand_data["recharge_time"] > _wand_bounds.recharge_time[2] then
+ _research.b_recharge_time = true;
+ _research.b_recharge_time_max = true;
+ _research.is_new = true;
+ _cost.recharge_time = math.ceil(__cost_func_recharge_time(_in_wand_data["recharge_time"]));
+ end
+ end
+ if _in_wand_data["mana_max"] > _wand_bounds.mana_max[2] then
+ _research.b_mana_max = true;
+ _research.is_new = true;
+ _cost.mana_max = math.ceil(__cost_func_mana_max(_in_wand_data["mana_max"]));
+ end
+ if _in_wand_data["mana_charge_speed"] > _wand_bounds.mana_charge_speed[2] then
+ _research.b_mana_charge_speed = true;
+ _research.is_new = true;
+ _cost.mana_charge_speed = math.ceil(__cost_func_mana_charge_speed(_in_wand_data["mana_charge_speed"]));
+ end
+ if _in_wand_data["capacity"] > _wand_bounds.capacity[2] then
+ _cost.capacity = math.ceil(__cost_func_capacity(_in_wand_data["capacity"]));
+ _research.b_capacity = true;
+ _research.is_new = true;
+ end
+ if _wand_bounds.spread[1] == nil or _wand_bounds.spread[2] == nil then
+ _research.b_spread = true;
+ _research.b_spread_min = true;
+ _research.b_spread_max = true;
+ _research.is_new = true;
+ _cost.spread = math.ceil(__cost_func_spread(_in_wand_data["spread"]));
+ else
+ if _in_wand_data["spread"] < _wand_bounds.spread[1] then
+ _research.b_spread = true;
+ _research.b_spread_min = true;
+ _research.is_new = true;
+ _cost.spread = math.ceil(__cost_func_spread(_in_wand_data["spread"]));
+ end
+ if _in_wand_data["spread"] > _wand_bounds.spread[2] then
+ _research.b_spread = true;
+ _research.b_spread_max = true;
+ _research.is_new = true;
+ _cost.spread = math.ceil(__cost_func_spread(_in_wand_data["spread"]));
+ end
+ end
+
+ if _in_wand_data["always_cast_spells"] ~= nil and #_in_wand_data["always_cast_spells"] > 0 then
+ for _, _always_cast_id in pairs(_in_wand_data["always_cast_spells"]) do
+ if actions_by_id[_always_cast_id] ~= nil and (_wand_bounds.always_casts == nil or _wand_bounds.always_casts[_always_cast_id] == nil) then
+ _research.i_always_cast_spells = _research.i_always_cast_spells + 1;
+ _research.b_always_cast_spells = true;
+ _research.is_new = true;
+ _cost.always_cast_spells = _cost.always_cast_spells + math.ceil(__cost_func_always_cast_spell(_always_cast_id));
+ end
+ end
+ if (#_in_wand_data["always_cast_spells"] or 0) > (_wand_bounds.always_cast_count or 0) then
+ _research.b_always_cast_count = true;
+ _research.is_new = true;
+ _cost.always_cast_count = math.ceil(__cost_func_always_cast_count(#_in_wand_data["always_cast_spells"]));
+ end
+ end
+
+ if _in_wand_data["spells"] ~= nil and #_in_wand_data["spells"] > 0 then
+ for _, _spell_id in pairs(_in_wand_data["spells"]) do
+ if actions_by_id[_spell_id] ~= nil and (_profile_known_spells == nil or _profile_known_spells[_spell_id] ~= true) then
+ _research.i_spells = _research.i_spells + 1;
+ _research.b_spells = true;
+ end
+ end
+ end
+
+ if _wand_bounds.wand_types == nil or _wand_bounds.wand_types[_in_wand_data["wand_type"]] == nil then
+ if wand_type_to_base_wand(_in_wand_data["wand_type"]) ~= nil then
+ _research.b_new_is_only_type = not _research.is_new;
+ _research.b_wand_types = true;
+ _research.is_new = true;
+ _cost.wand_type = math.ceil(__cost_func_wand_type(_in_wand_data["wand_type"]));
+ end
+ end
+ end -- if entity_id~=nil|0
+
+ local _sum = 0;
+ for _member_name, _member_cost in pairs(_cost) do
+ if _member_name=="always_cast_spells" then
+ _sum = _sum + math.ceil(_member_cost * mod_setting.research_spell_price_multiplier);
+ else
+ _sum = _sum + math.ceil(_member_cost * mod_setting.research_wand_price_multiplier);
+ end
+ end
+ _cost._sum = _sum;
+
+ return _research, _cost, _in_wand_data;
+ end
+
+ local function _research_wand(profile_id, entity_id)
+ local _research, _cost, _wand_data = _get_wand_entity_research(profile_id, entity_id);
+
+ if get_player_money() < _cost._sum then
+ return false;
+ end
+
+ if _research.b_spells_per_cast then _set_spells_per_cast(profile_id, _wand_data["spells_per_cast"]); end
+ if _research.b_cast_delay_min then _set_cast_delay_min(profile_id, _wand_data["cast_delay"]); end
+ if _research.b_cast_delay_max then _set_cast_delay_max(profile_id, _wand_data["cast_delay"]); end
+ if _research.b_recharge_time_min then _set_recharge_time_min(profile_id, _wand_data["recharge_time"]); end
+ if _research.b_recharge_time_max then _set_recharge_time_max(profile_id, _wand_data["recharge_time"]); end
+ if _research.b_mana_max then _set_mana_max(profile_id, _wand_data["mana_max"]); end
+ if _research.b_mana_charge_speed then _set_mana_charge_speed(profile_id, _wand_data["mana_charge_speed"]); end
+ if _research.b_capacity then _set_capacity(profile_id, _wand_data["capacity"]); end
+ if _research.b_spread_min then _set_spread_min(profile_id, _wand_data["spread"]); end
+ if _research.b_spread_max then _set_spread_max(profile_id, _wand_data["spread"]); end
+ if _research.b_always_cast_spells then _add_always_cast_spells(profile_id, _wand_data["always_cast_spells"]); end
+ if _research.b_always_cast_count then _set_always_cast_count(profile_id, #_wand_data["always_cast_spells"]); end
+ if _research.b_wand_types then _add_wand_types(profile_id, { _wand_data["wand_type"] }); end
+
+ delete_wand_entity(entity_id);
+ decrement_player_money(_cost._sum);
+ return true;
+ end
+
+ local function _research_spell_entity(profile_id, entity_id)
+ local price = get_spell_entity_research_price(entity_id);
+ if (price == nil) or (get_player_money() < price) then
+ return false;
+ end
+
+ _add_spells(profile_id, { get_spell_entity_action_id(entity_id) });
+
+ delete_spell_entity(entity_id);
+ decrement_player_money(price);
+ return true;
+ end
+
+ ---- ===========================
+ ---- PRIVATE TO PUBLIC PROMOTION
+ ---- ===========================
+
+ function get_stash_money() return _get_stash_money(loaded_profile_id); end
+ function set_stash_money(value) return _set_stash_money(loaded_profile_id, value); end
+ ---increments stash money, returns success
+ ---@param amount integer money
+ ---@return boolean success
+ function increment_stash_money(amount) return _increment_stash_money(loaded_profile_id, amount); end
+ ---increments stash money, returns success
+ ---@param amount integer money
+ ---@return boolean success
+ function decrement_stash_money(amount) return _decrement_stash_money(loaded_profile_id, amount); end
+ function transfer_money_player_to_stash(value) return _transfer_money_player_to_stash(loaded_profile_id, value); end
+ function transfer_money_stash_to_player(value) return _transfer_money_stash_to_player(loaded_profile_id, value); end
+ function get_profile_spells() return _get_profile_spells(loaded_profile_id); end
+ function research_spell_entity(entity_id) return _research_spell_entity(loaded_profile_id, entity_id); end
+ function research_wand(entity_id) return _research_wand(loaded_profile_id, entity_id); end
+ function get_templates() return _get_templates(loaded_profile_id); end
+ function get_template(template_id) return _get_template(loaded_profile_id, template_id); end
+ function load_templates() return _load_templates(loaded_profile_id); end
+ function set_template(template_id, wand_data) _set_template(loaded_profile_id, template_id, wand_data); end
+ function delete_template(template_id) _delete_template(loaded_profile_id, template_id); end
+ function get_always_cast_spells() return _get_always_cast_spells(loaded_profile_id); end
+ function get_always_cast_count() return _get_always_cast_count(loaded_profile_id); end
+ function get_wand_bounds() return _get_wand_bounds(loaded_profile_id); end
+ function get_wand_entity_research(entity_id) return _get_wand_entity_research(loaded_profile_id, entity_id); end
+
+ ---- ================
+ ---- PUBLIC FUNCTIONS
+ ---- ================
+
+ function does_profile_know_spell(action_id) return data_store[loaded_profile_id]["spells"][action_id] or false; end
+
+ function get_modify_wand_table(entity_id)
+ local _members = {"spells_per_cast", "cast_delay", "recharge_time", "mana_max", "mana_charge_speed", "spread", "capacity"};
+
+ local _template = get_template(1);
+ local _table = { origin_entity = entity_id, bounds = get_wand_bounds() };
+ if _template.capacity~=nil and (entity_id==nil or entity_id==0) then
+ _table.wand = {};
+ for _member, _data in pairs(_template) do
+ _table.wand[_member] = _data;
+ end
+ else
+ _table.wand = read_wand_entity(entity_id);
+ _table.price = get_wand_buy_price(_table.wand);
+ _table.origin_e_id = entity_id;
+ end
+ if _table.wand.wand_type==-1 then
+ _table.wand.wand_type = data_store[loaded_profile_id]["wand_types_idx"][1];
+ _table.wand.sprite = wand_type_to_sprite_file(_table.wand.wand_type);
+ _table.wand.shuffle = false;
+ for _, _member in ipairs(_members) do
+ _table.wand[_member] = math.floor( (_table.bounds[_member][1] + _table.bounds[_member][2]) / 2 );
+ end
+ end
+ _table.ac_limit = _table.bounds.always_cast_count;
+ _table.ac_spells = get_always_cast_spell_purchase_table();
+ return _table;
+ end
+
+ -- PROFILES
+
+ ---erase profile slot and write new, clear in-memory datastore for profile, tag profile as selected
+ ---@param profile_id integer
+ function create_new_profile(profile_id, _force)
+ _force = _force==true or false;
+ if data_store[profile_id]~=nil and data_store[profile_id].quickloaded==true and not _force then return; end
+
+ delete_profile(profile_id);
+ load_profile(profile_id);
+ encoder_add_flag(tostring(profile_id));
+ selected_profile_id=profile_id;
+ end
+
+ ---erase profile slot and mark empty, clear in-memory datastore for profile
+ ---@param profile_id integer
+ function delete_profile(profile_id)
+ encoder_clear_integer(profile_id .. "_spells_per_cast");
+ encoder_clear_integer(profile_id .. "_cast_delay_min");
+ encoder_clear_integer(profile_id .. "_cast_delay_max");
+ encoder_clear_integer(profile_id .. "_recharge_time_min");
+ encoder_clear_integer(profile_id .. "_recharge_time_max");
+ encoder_clear_integer(profile_id .. "_mana_max");
+ encoder_clear_integer(profile_id .. "_capacity");
+ encoder_clear_integer(profile_id .. "_always_cast_count");
+ encoder_clear_integer(profile_id .. "_spread_min");
+ encoder_clear_integer(profile_id .. "_spread_max");
+ encoder_clear_integer(profile_id .. "_money");
+ encoder_clear_integer(profile_id .. "_always_cast_spells_known");
+ encoder_clear_integer(profile_id .. "_spells_known");
+ encoder_clear_integer(profile_id .. "_wand_types_known");
+ for _, curr_action in ipairs(actions_by_id) do
+ encoder_clear_flag(profile_id .. "_spell_" .. string.lower(curr_action.id));
+ encoder_clear_flag(profile_id .. "_always_cast_spell_" .. string.lower(curr_action.id));
+ end
+ for _, curr_wand in ipairs(wands) do
+ encoder_clear_flag(profile_id .. "_wand_type_" .. sprite_file_to_wand_type(curr_wand.file));
+ end
+
+ for i = 1, get_template_count() do
+ _delete_template(profile_id, i);
+ end
+
+ encoder_clear_flag(tostring(profile_id)); -- remove profile exists tag
+ data_store[profile_id] = nil;
+ end
+
+ ---return byval copy of datastore for quickloaded profiles
+ ---@return table {quickloaded, money, always_cast_spells_known, wand_types_known}
+ function get_quick_profiles()
+ clean_store = {};
+ for profile_id = 1, get_profile_count() do
+ clean_store[profile_id] = {};
+ if data_store[profile_id]~=nil and data_store[profile_id]["quickloaded"]==true then
+ clean_store[profile_id]["quickloaded"] = data_store[profile_id]["quickloaded"];
+ clean_store[profile_id]["money"] = data_store[profile_id]["money"];
+ clean_store[profile_id]["always_cast_spells_known"] = data_store[profile_id]["always_cast_spells_known"];
+ clean_store[profile_id]["spells_known"] = data_store[profile_id]["spells_known"];
+ clean_store[profile_id]["wand_types_known"] = data_store[profile_id]["wand_types_known"];
+ else
+ clean_store[profile_id]["quickloaded"] = nil;
+ end
+ end
+ return clean_store;
+ end
+
+ ---load profile_id from disk to data_store
+ ---@param profile_id number
+ function load_profile(profile_id)
+ for to_clear = 1, get_profile_count() do
+ data_store[to_clear] = {}
+ end
+
+ _load_profile_quick(profile_id);
+
+ data_store[profile_id]["spells_per_cast"] = encoder_load_integer(profile_id .. "_spells_per_cast");
+ data_store[profile_id]["cast_delay_min"] = encoder_load_integer(profile_id .. "_cast_delay_min");
+ data_store[profile_id]["cast_delay_max"] = encoder_load_integer(profile_id .. "_cast_delay_max");
+ data_store[profile_id]["recharge_time_min"] = encoder_load_integer(profile_id .. "_recharge_time_min");
+ data_store[profile_id]["recharge_time_max"] = encoder_load_integer(profile_id .. "_recharge_time_max");
+ data_store[profile_id]["mana_max"] = encoder_load_integer(profile_id .. "_mana_max");
+ data_store[profile_id]["mana_charge_speed"] = encoder_load_integer(profile_id .. "_mana_charge_speed");
+ data_store[profile_id]["always_cast_count"] = encoder_load_integer(profile_id .. "_always_cast_count");
+ data_store[profile_id]["capacity"] = encoder_load_integer(profile_id .. "_capacity");
+ local spread_min = encoder_load_integer(profile_id .. "_spread_min");
+ if spread_min ~= nil then
+ spread_min = spread_min / 10;
+ end
+ data_store[profile_id]["spread_min"] = spread_min;
+ local spread_max = encoder_load_integer(profile_id .. "_spread_max");
+ if spread_max ~= nil then
+ spread_max = spread_max / 10;
+ end
+ data_store[profile_id]["spread_max"] = spread_max;
+
+
+ _load_profile_spells(profile_id);
+ _load_wand_types(profile_id);
+ _load_templates(profile_id);
+
+ data_store[profile_id]["loaded"] = true;
+
+ print("=========================");
+ print("persistence: Loaded profile " .. profile_id);
+ print("money: " .. data_store[profile_id]["money"]);
+ print("always_cast_spells_known: " .. data_store[profile_id]["always_cast_spells_known"]);
+ print("spells_known: " .. data_store[profile_id]["spells_known"]);
+ print("wand_types_known: " .. data_store[profile_id]["wand_types_known"]);
+ loaded_profile_id = profile_id;
+ GlobalsSetValue("persistence_profile", tostring(profile_id));
+ end
+
+ function data_store_everyframe()
+ if selected_profile_id>0 and loaded_profile_id~=selected_profile_id then load_profile(selected_profile_id); end
+
+ if loaded_profile_id==0 then return; end
+
+ local _game_frame = GameGetFrameNum();
+
+ if _game_frame%60 then
+ _do_startup_paycheck_check();
+ end
+
+ if _game_frame%30==0 and _in_workshop then
+ _do_holy_mountain_paycheck_check();
+ end
+ end
+ ---end function declarations, first-run code here;
+
+ for profile_idx = 1, get_profile_count() do
+ data_store[profile_idx]=data_store[profile_idx] or {};
+ if encoder_has_flag(tostring(profile_idx)) then
+ _load_profile_quick(profile_idx)
+ end
+ end
+
+ if GlobalsGetValue("persistence_profile", "0")~="0" then
+ selected_profile_id = tonumber(GlobalsGetValue("persistence_profile", "0"));
+ elseif GameHasFlagRun("persistence_using_mod") then
+ for i = 1, get_profile_count() do
+ if GameHasFlagRun("persistence_selected_profile_" .. tostring(i)) then
+ selected_profile_id = i;
+ break;
end
- data_store[save_id]["templates"][i]["spells_per_cast"] = hex_to_number(load_hex(save_id_string .. "_template_" .. tostring(i) .. "_spells_per_cast"));
- data_store[save_id]["templates"][i]["cast_delay"] = hex_to_number(load_hex(save_id_string .. "_template_" .. tostring(i) .. "_cast_delay"));
- data_store[save_id]["templates"][i]["recharge_time"] = hex_to_number(load_hex(save_id_string .. "_template_" .. tostring(i) .. "_recharge_time"));
- data_store[save_id]["templates"][i]["mana_max"] = hex_to_number(load_hex(save_id_string .. "_template_" .. tostring(i) .. "_mana_max"));
- data_store[save_id]["templates"][i]["mana_charge_speed"] = hex_to_number(load_hex(save_id_string .. "_template_" .. tostring(i) .. "_mana_charge_speed"));
- data_store[save_id]["templates"][i]["capacity"] = hex_to_number(load_hex(save_id_string .. "_template_" .. tostring(i) .. "_capacity"));
- data_store[save_id]["templates"][i]["spread"] = hex_to_number(load_hex(save_id_string .. "_template_" .. tostring(i) .. "_spread")) / 10;
-
- data_store[save_id]["templates"][i]["always_cast_spells"] = {};
- for key, _ in pairs(data_store[save_id]["always_cast_spells"]) do
- if HasFlagPersistent(flag_prefix .. "_" .. save_id_string .. "_template_" .. tostring(i) .. "_always_cast_spell_" .. string.lower(key)) then
- table.insert(data_store[save_id]["templates"][i]["always_cast_spells"], key);
- break;
- end
- end
-
- for key, _ in pairs(data_store[save_id]["wand_types"]) do
- if HasFlagPersistent(flag_prefix .. "_" .. save_id_string .. "_template_" .. tostring(i) .. "_wand_type_" .. string.lower(key)) then
- data_store[save_id]["templates"][i]["wand_type"] = key;
- break;
- end
- end
- end
- end
-end
-
--- spells per cast
-function get_spells_per_cast(save_id)
- if data_store[save_id] == nil then
- return nil;
- end
- return data_store[save_id]["spells_per_cast"] == nil and spells_per_cast_min or data_store[save_id]["spells_per_cast"];
-end
-
-local function set_spells_per_cast(save_id, value)
- if data_store[save_id] == nil then
- return;
- end
- data_store[save_id]["spells_per_cast"] = value;
- save_hex(tostring(save_id) .. "_spells_per_cast", number_to_hex(data_store[save_id]["spells_per_cast"]));
-end
-
--- cast delay min
-function get_cast_delay_min(save_id)
- if data_store[save_id] == nil then
- return nil;
- end
- return data_store[save_id]["cast_delay_min"];
-end
-
-local function set_cast_delay_min(save_id, value)
- if data_store[save_id] == nil then
- return;
- end
- data_store[save_id]["cast_delay_min"] = value;
- save_hex(tostring(save_id) .. "_cast_delay_min", number_to_hex(data_store[save_id]["cast_delay_min"]));
-end
-
--- cast delay max
-function get_cast_delay_max(save_id)
- if data_store[save_id] == nil then
- return nil;
- end
- return data_store[save_id]["cast_delay_max"];
-end
-
-local function set_cast_delay_max(save_id, value)
- if data_store[save_id] == nil then
- return;
- end
- data_store[save_id]["cast_delay_max"] = value;
- save_hex(tostring(save_id) .. "_cast_delay_max", number_to_hex(data_store[save_id]["cast_delay_max"]));
-end
-
--- recharge time min
-function get_recharge_time_min(save_id)
- if data_store[save_id] == nil then
- return nil;
- end
- return data_store[save_id]["recharge_time_min"];
-end
-
-local function set_recharge_time_min(save_id, value)
- if data_store[save_id] == nil then
- return;
- end
- data_store[save_id]["recharge_time_min"] = value;
- save_hex(tostring(save_id) .. "_recharge_time_min", number_to_hex(data_store[save_id]["recharge_time_min"]));
-end
-
--- recharge time max
-function get_recharge_time_max(save_id)
- if data_store[save_id] == nil then
- return nil;
- end
- return data_store[save_id]["recharge_time_max"];
-end
-
-local function set_recharge_time_max(save_id, value)
- if data_store[save_id] == nil then
- return;
- end
- data_store[save_id]["recharge_time_max"] = value;
- save_hex(tostring(save_id) .. "_recharge_time_max", number_to_hex(data_store[save_id]["recharge_time_max"]));
-end
-
--- mana max
-function get_mana_max(save_id)
- if data_store[save_id] == nil then
- return nil;
- end
- return data_store[save_id]["mana_max"] == nil and mana_max_min or data_store[save_id]["mana_max"];
-end
-
-local function set_mana_max(save_id, value)
- if data_store[save_id] == nil then
- return;
- end
- data_store[save_id]["mana_max"] = value;
- save_hex(tostring(save_id) .. "_mana_max", number_to_hex(data_store[save_id]["mana_max"]));
-end
-
--- mana charge speed
-function get_mana_charge_speed(save_id)
- if data_store[save_id] == nil then
- return nil;
- end
- return data_store[save_id]["mana_charge_speed"] == nil and mana_charge_speed_min or data_store[save_id]["mana_charge_speed"];
-end
-
-local function set_mana_charge_speed(save_id, value)
- if data_store[save_id] == nil then
- return;
- end
- data_store[save_id]["mana_charge_speed"] = value;
- save_hex(tostring(save_id) .. "_mana_charge_speed", number_to_hex(data_store[save_id]["mana_charge_speed"]));
-end
-
--- capacity
-function get_capacity(save_id)
- if data_store[save_id] == nil then
- return nil;
- end
- return data_store[save_id]["capacity"] == nil and capacity_min or data_store[save_id]["capacity"];
-end
-
-local function set_capacity(save_id, value)
- if data_store[save_id] == nil then
- return;
- end
- data_store[save_id]["capacity"] = value;
- save_hex(tostring(save_id) .. "_capacity", number_to_hex(data_store[save_id]["capacity"]));
-end
-
--- spread min
-function get_spread_min(save_id)
- if data_store[save_id] == nil then
- return nil;
- end
- return data_store[save_id]["spread_min"];
-end
-
-local function set_spread_min(save_id, value)
- if data_store[save_id] == nil then
- return;
- end
- data_store[save_id]["spread_min"] = value;
- save_hex(tostring(save_id) .. "_spread_min", number_to_hex(data_store[save_id]["spread_min"] == nil and nil or math.floor(data_store[save_id]["spread_min"] * 10)));
-end
-
--- spread max
-function get_spread_max(save_id)
- if data_store[save_id] == nil then
- return nil;
- end
- return data_store[save_id]["spread_max"];
-end
-
-local function set_spread_max(save_id, value)
- if data_store[save_id] == nil then
- return;
- end
- data_store[save_id]["spread_max"] = value;
- save_hex(tostring(save_id) .. "_spread_max", number_to_hex(data_store[save_id]["spread_max"] == nil and nil or math.ceil(data_store[save_id]["spread_max"] * 10)));
-end
-
--- money
-function get_safe_money(save_id)
- if data_store[save_id] == nil then
- return nil;
- end
- return data_store[save_id]["money"] == nil and 0 or data_store[save_id]["money"];
-end
-
-function set_safe_money(save_id, value)
- if data_store[save_id] == nil then
- return;
- end
- data_store[save_id]["money"] = value;
- save_hex(tostring(save_id) .. "_money", number_to_hex(data_store[save_id]["money"]));
-end
-
--- spells
-function get_spells(save_id)
- if data_store[save_id] == nil then
- return nil;
- end
- if data_store[save_id]["spells"] == nil then
- return {};
- end
- return data_store[save_id]["spells"];
-end
-
-local function add_spells(save_id, spells)
- if data_store[save_id] == nil or spells == nil or #spells == 0 then
- return;
- end
- if data_store[save_id]["spells"] == nil then
- data_store[save_id]["spells"] = {};
- end
- for i = 1, #spells do
- data_store[save_id]["spells"][spells[i]] = true;
- AddFlagPersistent(flag_prefix .. "_" .. tostring(save_id) .. "_spell_" .. string.lower(spells[i]));
- end
-end
-
--- always cast spells
-function get_always_cast_spells(save_id)
- if data_store[save_id] == nil then
- return nil;
- end
- if data_store[save_id]["always_cast_spells"] == nil then
- return {};
- end
- return data_store[save_id]["always_cast_spells"];
-end
-
-local function add_always_cast_spells(save_id, spells)
- if data_store[save_id] == nil or spells == nil or #spells == 0 then
- return;
- end
- if data_store[save_id]["always_cast_spells"] == nil then
- data_store[save_id]["always_cast_spells"] = {};
- end
- for i = 1, #spells do
- data_store[save_id]["always_cast_spells"][spells[i]] = true;
- AddFlagPersistent(flag_prefix .. "_" .. tostring(save_id) .. "_always_cast_spell_" .. string.lower(spells[i]));
- end
-end
-
--- wand types
-function get_wand_types(save_id)
- if data_store[save_id] == nil then
- return nil;
- end
- if data_store[save_id]["wand_types"] == nil then
- return {};
- end
- return data_store[save_id]["wand_types"];
-end
-
-local function add_wand_types(save_id, wand_types)
- if data_store[save_id] == nil or wand_types == nil or #wand_types == 0 then
- return;
- end
- if data_store[save_id]["wand_types"] == nil then
- data_store[save_id]["wand_types"] = {};
- end
- for i = 1, #wand_types do
- if string.sub(wand_types[i], 1, #"default") ~= "default" then
- data_store[save_id]["wand_types"][wand_types[i]] = true;
- AddFlagPersistent(flag_prefix .. "_" .. tostring(save_id) .. "_wand_type_" .. string.lower(wand_types[i]));
- end
- end
-end
-
--- templates
-function get_template(save_id, template_id)
- if data_store[save_id] == nil or data_store[save_id]["templates"] == nil then
- return nil;
- end
- return data_store[save_id]["templates"][template_id];
-end
-
-function set_template(save_id, template_id, wand_data)
- if data_store[save_id] == nil or data_store[save_id]["templates"] == nil then
- return;
- end
- delete_template(save_id, template_id);
- if wand_data == nil then
- return;
- end
- local template_prefix = tostring(save_id) .. "_template_" .. tostring(template_id);
- local template_flag_prefix = flag_prefix .. "_" .. template_prefix;
- if wand_data["shuffle"] then
- AddFlagPersistent(template_flag_prefix .. "_shuffle");
- end
- save_hex(template_prefix .. "_spells_per_cast", number_to_hex(wand_data["spells_per_cast"]));
- save_hex(template_prefix .. "_cast_delay", number_to_hex(wand_data["cast_delay"]));
- save_hex(template_prefix .. "_recharge_time", number_to_hex(wand_data["recharge_time"]));
- save_hex(template_prefix .. "_mana_max", number_to_hex(wand_data["mana_max"]));
- save_hex(template_prefix .. "_mana_charge_speed", number_to_hex(wand_data["mana_charge_speed"]));
- save_hex(template_prefix .. "_capacity", number_to_hex(wand_data["capacity"]));
- save_hex(template_prefix .. "_spread", number_to_hex(math.floor(wand_data["spread"] * 10 + 0.5)));
-
- for _, spell in ipairs(wand_data["always_cast_spells"]) do
- AddFlagPersistent(template_flag_prefix .. "_always_cast_spell_" .. string.lower(spell));
- end
-
- AddFlagPersistent(template_flag_prefix .. "_wand_type_" .. string.lower(wand_data["wand_type"]));
-
- AddFlagPersistent(template_flag_prefix);
- data_store[save_id]["templates"][template_id] = wand_data;
-end
-
-function delete_template(save_id, template_id)
- if data_store[save_id] == nil then
- return;
- end
- local template_prefix = tostring(save_id) .. "_template_" .. tostring(template_id);
- local template_flag_prefix = flag_prefix .. "_" .. template_prefix;
- RemoveFlagPersistent(template_flag_prefix .. "_shuffle");
- save_hex(template_prefix .. "_spells_per_cast", nil);
- save_hex(template_prefix .. "_cast_delay", nil);
- save_hex(template_prefix .. "_recharge_time", nil);
- save_hex(template_prefix .. "_mana_max", nil);
- save_hex(template_prefix .. "_mana_charge_speed", nil);
- save_hex(template_prefix .. "_capacity", nil);
- save_hex(template_prefix .. "_spread", nil);
-
- for i = 1, #actions do
- RemoveFlagPersistent(template_flag_prefix .. "_always_cast_spell_" .. string.lower(actions[i].id));
- end
-
- for i = 1, #mod_config.default_wands do
- RemoveFlagPersistent(template_flag_prefix .. "_wand_type_default_" .. tostring(i));
- end
- for i = 1, #wands do
- RemoveFlagPersistent(template_flag_prefix .. "_wand_type_" .. string.lower(sprite_file_to_wand_type(wands[i].file)));
- end
-
- RemoveFlagPersistent(template_flag_prefix);
- if data_store[save_id]["templates"] ~= nil then
- data_store[save_id]["templates"][template_id] = nil;
- end
-end
-
-function can_create_wand(save_id)
- return get_cast_delay_min(save_id) ~= nil and get_cast_delay_max(save_id) ~= nil and get_recharge_time_min(save_id) ~= nil and get_recharge_time_max(save_id) ~= nil and get_spread_min(save_id) ~= nil and get_spread_max(save_id) ~= nil;
-end
-
-function research_wand_is_new(save_id, entity_id)
- local wand_data = read_wand(entity_id);
- local spells_per_cast = get_spells_per_cast(save_id);
- local cast_delay_min = get_cast_delay_min(save_id);
- local cast_delay_max = get_cast_delay_max(save_id);
- local recharge_time_min = get_recharge_time_min(save_id);
- local recharge_time_max = get_recharge_time_max(save_id);
- local mana_max = get_mana_max(save_id);
- local mana_charge_speed = get_mana_charge_speed(save_id);
- local capacity = get_capacity(save_id);
- local spread_min = get_spread_min(save_id);
- local spread_max = get_spread_max(save_id);
- local always_cast_spells = get_always_cast_spells(save_id);
- local wand_types = get_wand_types(save_id);
-
- if wand_data["spells_per_cast"] > spells_per_cast then
- return true;
- end
- if cast_delay_min == nil or cast_delay_max == nil then
- return true;
- else
- if wand_data["cast_delay"] < cast_delay_min then
- return true;
- end
- if wand_data["cast_delay"] > cast_delay_max then
- return true;
- end
- end
- if recharge_time_min == nil or recharge_time_max == nil then
- return true;
- else
- if wand_data["recharge_time"] < recharge_time_min then
- return true;
- end
- if wand_data["recharge_time"] > recharge_time_max then
- return true;
- end
- end
- if wand_data["mana_max"] > mana_max then
- return true;
- end
- if wand_data["mana_charge_speed"] > mana_charge_speed then
- return true;
- end
- if wand_data["capacity"] > capacity then
- return true;
- end
- if spread_min == nil or spread_max == nil then
- return true;
- else
- if wand_data["spread"] < spread_min then
- return true;
- end
- if wand_data["spread"] > spread_max then
- return true;
- end
- end
- if wand_data["always_cast_spells"] ~= nil and #wand_data["always_cast_spells"] > 0 then
- for i = 1, #wand_data["always_cast_spells"] do
- if always_cast_spells[wand_data["always_cast_spells"][i]] == nil then
- for j = 1, #actions do
- if actions[j].id == wand_data["always_cast_spells"][i] then
- return true;
- end
- end
- end
- end
- end
- if wand_types[wand_data["wand_type"]] == nil then
- if wand_type_to_wand(wand_data["wand_type"]) ~= nil then
- return true;
- end
- end
-
- return false;
-end
-
-function research_wand_price(save_id, entity_id)
- local wand_data = read_wand(entity_id);
- local spells_per_cast = get_spells_per_cast(save_id);
- local cast_delay_min = get_cast_delay_min(save_id);
- local cast_delay_max = get_cast_delay_max(save_id);
- local recharge_time_min = get_recharge_time_min(save_id);
- local recharge_time_max = get_recharge_time_max(save_id);
- local mana_max = get_mana_max(save_id);
- local mana_charge_speed = get_mana_charge_speed(save_id);
- local capacity = get_capacity(save_id);
- local spread_min = get_spread_min(save_id);
- local spread_max = get_spread_max(save_id);
- local always_cast_spells = get_always_cast_spells(save_id);
- local wand_types = get_wand_types(save_id);
- local price = 0;
-
- if wand_data["spells_per_cast"] > spells_per_cast then
- price = price + (wand_data["spells_per_cast"] - spells_per_cast) * 1000;
- end
- if cast_delay_min == nil or cast_delay_max == nil then
- price = price + 0.01 ^ (wand_data["cast_delay"] / 60 - 1.8) + 200;
- else
- if wand_data["cast_delay"] < cast_delay_min then
- price = price + (0.01 ^ (wand_data["cast_delay"] / 60 - 1.8) + 200) - (0.01 ^ (cast_delay_min / 60 - 1.8) + 200);
- end
- if wand_data["cast_delay"] > cast_delay_max then
- price = price + (wand_data["cast_delay"] / 60 - cast_delay_max / 60) * 100;
- end
- end
- if recharge_time_min == nil or recharge_time_max == nil then
- price = price + 0.01 ^ (wand_data["recharge_time"] / 60 - 1.8) + 200;
- else
- if wand_data["recharge_time"] < recharge_time_min then
- price = price + (0.01 ^ (wand_data["recharge_time"] / 60 - 1.8) + 200) - (0.01 ^ (recharge_time_min / 60 - 1.8) + 200);
- end
- if wand_data["recharge_time"] > recharge_time_max then
- price = price + (wand_data["recharge_time"] / 60 - recharge_time_max / 60) * 100;
- end
- end
- if wand_data["mana_max"] > mana_max then
- price = price + (wand_data["mana_max"] - mana_max) * 10;
- end
- if wand_data["mana_charge_speed"] > mana_charge_speed then
- price = price + (wand_data["mana_charge_speed"] - mana_charge_speed) * 20;
- end
- if wand_data["capacity"] > capacity then
- price = price + (wand_data["capacity"] - capacity) * 1000;
- end
- if spread_min == nil or spread_max == nil then
- price = price + math.abs(5 - wand_data["spread"]) * 10;
- else
- if wand_data["spread"] < spread_min then
- price = price + (spread_min - wand_data["spread"]) * 10;
- end
- if wand_data["spread"] > spread_max then
- price = price + (wand_data["spread"] - spread_max) * 10;
- end
- end
- if wand_data["always_cast_spells"] ~= nil and #wand_data["always_cast_spells"] > 0 then
- for i = 1, #wand_data["always_cast_spells"] do
- if always_cast_spells[wand_data["always_cast_spells"][i]] == nil then
- for j = 1, #actions do
- if actions[j].id == wand_data["always_cast_spells"][i] then
- price = price + actions[j].price * 20;
- break;
- end
- end
- end
- end
- end
- if wand_types[wand_data["wand_type"]] == nil then
- if wand_type_to_wand(wand_data["wand_type"]) ~= nil then
- price = math.max(100, price);
- end
- end
-
- return math.ceil(price * mod_config.research_wand_price_multiplier);
-end
-
-function research_wand(save_id, entity_id)
- local wand_data = read_wand(entity_id);
- local spells_per_cast = get_spells_per_cast(save_id);
- local cast_delay_min = get_cast_delay_min(save_id);
- local cast_delay_max = get_cast_delay_max(save_id);
- local recharge_time_min = get_recharge_time_min(save_id);
- local recharge_time_max = get_recharge_time_max(save_id);
- local mana_max = get_mana_max(save_id);
- local mana_charge_speed = get_mana_charge_speed(save_id);
- local capacity = get_capacity(save_id);
- local spread_min = get_spread_min(save_id);
- local spread_max = get_spread_max(save_id);
-
- local price = research_wand_price(save_id, entity_id);
- if get_player_money() < price then
- return false;
- end
-
- if wand_data["spells_per_cast"] > spells_per_cast then
- set_spells_per_cast(save_id, wand_data["spells_per_cast"]);
- end
- if cast_delay_min == nil or wand_data["cast_delay"] < cast_delay_min then
- set_cast_delay_min(save_id, wand_data["cast_delay"]);
- end
- if cast_delay_max == nil or wand_data["cast_delay"] > cast_delay_max then
- set_cast_delay_max(save_id, wand_data["cast_delay"]);
- end
- if recharge_time_min == nil or wand_data["recharge_time"] < recharge_time_min then
- set_recharge_time_min(save_id, wand_data["recharge_time"]);
- end
- if recharge_time_max == nil or wand_data["recharge_time"] > recharge_time_max then
- set_recharge_time_max(save_id, wand_data["recharge_time"]);
- end
- if wand_data["mana_max"] > mana_max then
- set_mana_max(save_id, wand_data["mana_max"]);
- end
- if wand_data["mana_charge_speed"] > mana_charge_speed then
- set_mana_charge_speed(save_id, wand_data["mana_charge_speed"]);
- end
- if wand_data["capacity"] > capacity then
- set_capacity(save_id, wand_data["capacity"]);
- end
- if spread_min == nil or wand_data["spread"] < spread_min then
- set_spread_min(save_id, wand_data["spread"]);
- end
- if spread_max == nil or wand_data["spread"] > spread_max then
- set_spread_max(save_id, wand_data["spread"]);
- end
- if wand_data["always_cast_spells"] ~= nil and #wand_data["always_cast_spells"] > 0 then
- add_always_cast_spells(save_id, wand_data["always_cast_spells"]);
- end
- if wand_type_to_wand(wand_data["wand_type"]) ~= nil then
- add_wand_types(save_id, { wand_data["wand_type"] });
- end
-
- delete_wand(entity_id);
- set_player_money(get_player_money() - price);
- return true;
-end
-
-function research_spell_price(entity_id)
- local action_id = read_spell(entity_id);
- if action_id == nil then
- return nil
- end
- for i = 1, #actions do
- if actions[i].id == action_id then
- return math.ceil(actions[i].price * mod_config.research_spell_price_multiplier);
end
- end
-end
-
-function research_spell(save_id, entity_id)
- local price = research_spell_price(entity_id);
- if price == nil then
- return false;
- end
- if get_player_money() < price then
- return false;
- end
-
- add_spells(save_id, { read_spell(entity_id) });
-
- delete_spell(entity_id);
- set_player_money(get_player_money() - price);
- return true;
-end
-
-function transfer_money_to_safe(save_id, amount)
- if get_player_money() < amount then
- return false;
- end
-
- set_safe_money(save_id, get_safe_money(save_id) + amount);
- set_player_money(get_player_money() - amount);
- return true;
-end
-
-function transfer_money_to_player(save_id, amount)
- if get_safe_money(save_id) < amount then
- return false;
- end
-
- set_player_money(get_player_money() + amount);
- set_safe_money(save_id, get_safe_money(save_id) - amount);
- return true;
-end
\ No newline at end of file
+ elseif default_profile_id>0 then
+ selected_profile_id = default_profile_id;
+ end
+
+ print("=========================");
+ print("persistence: Datastore loaded.");
+ persistence_data_store_loaded = true;
+end -- if persistence_data_store_loaded==false;
\ No newline at end of file
diff --git a/files/debug.lua b/files/debug.lua
deleted file mode 100644
index 09e1c9c..0000000
--- a/files/debug.lua
+++ /dev/null
@@ -1,64 +0,0 @@
-function str(var)
- if type(var) == 'table' then
- local s = '{\n'
- for k,v in pairs(var) do
- if type(k) ~= 'number' then k = '"'..k..'"' end
- s = s .. '\t['..k..'] = ' .. string.gsub(str(v), "\n", "\n\t") .. ',\n'
- end
- return s .. '}'
- end
- return tostring(var)
-end
-
-
-function debug_entity(e)
- local parent = EntityGetParent(e)
- local children = EntityGetAllChildren(e)
- local comps = EntityGetAllComponents(e)
-
- print("--- ENTITY DATA ---")
- print("Parent: ["..parent.."] " .. (EntityGetName(parent) or "nil"))
-
- print(" Entity: ["..tostring(e).."] " .. (EntityGetName(e) or "nil"))
- print(" Tags: " .. (EntityGetTags(e) or "nil"))
- if (comps ~= nil) then
- for _, comp in ipairs(comps) do
- print(" Comp: ["..comp.."] " .. (ComponentGetTypeName(comp) or "nil"))
- if comp ~= nil then
- for member_key, member_val in pairs(ComponentGetMembers(comp)) do
- --[[if member_val == "" then
- print(" [" .. member_key .. "]:")
- for object_key, object_val in pairs(ComponentObjectGetMembers(comp, member_key)) do
- print(" [" .. object_key .. "]: " .. (object_val or "nil"))
- end
- else]]--
- print(" [" .. member_key .. "]: " .. (member_val or "nil"))
- --end
- end
- end
- end
- end
-
- if children == nil then return end
-
- for _, child in ipairs(children) do
- local comps = EntityGetAllComponents(child)
- print(" Child: ["..child.."] " .. EntityGetName(child))
- for _, comp in ipairs(comps) do
- print(" Comp: ["..comp.."] " .. (ComponentGetTypeName(comp) or "nil"))
- if comp ~= nil then
- for member_key, member_val in pairs(ComponentGetMembers(comp)) do
- --[[if member_val == "" then
- print(" [" .. member_key .. "]:")
- for object_key, object_val in pairs(ComponentObjectGetMembers(comp, member_key)) do
- print(" [" .. object_key .. "]: " .. (object_val or "nil"))
- end
- else]]--
- print(" [" .. member_key .. "]: " .. (member_val or "nil"))
- --end
- end
- end
- end
- end
- print("--- END ENTITY DATA ---")
-end
\ No newline at end of file
diff --git a/files/edit_wands_in_lobby.xml b/files/edit_wands_in_lobby.xml
deleted file mode 100644
index e56c417..0000000
--- a/files/edit_wands_in_lobby.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/files/encoder.lua b/files/encoder.lua
new file mode 100644
index 0000000..78248e0
--- /dev/null
+++ b/files/encoder.lua
@@ -0,0 +1,146 @@
+-- On load only;
+if persistence_encoder_loaded~=true then
+ encoder_read_only = false;
+
+ ---encode number to hex string for saving
+ ---@param number integer
+ ---@return string hex_data
+ local function number_to_hex(number)
+ if number == nil then return ""; end
+
+ local positive = math.abs(number);
+ return (positive == number and "" or "-") .. string.format("%x", positive);
+ end
+
+ ---decode hex string to number
+ ---@param hex string hex_data
+ ---@return number
+ local function hex_to_number(hex)
+ if hex == nil then return 0; end
+
+ if string.sub(hex, 1, 1) == "-" then
+ return tonumber(string.sub(hex, 2), 16) * -1;
+ else
+ return tonumber(hex, 16);
+ end
+ end
+
+ local hex_chars = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "-" }
+ ---encode hex value and write to disk under named tag (mod_flag_name will be prepended)
+ ---@param name string name of psuedo-variable
+ ---@param hex string|nil
+ local function write_encode_hex(name, hex)
+ -- print(string.format("write_encode '%s' '%s'", name, hex))
+ if encoder_read_only then
+ -- print("suppressed");
+ return;
+ end
+
+ if hex == nil then
+ local i=1;
+ repeat
+ local hex_found = false;
+ for j = 1, #hex_chars do
+ if HasFlagPersistent(mod_flag_name .. "_" .. name .. "_" .. i .. "_" .. hex_chars[j]) then
+ RemoveFlagPersistent(mod_flag_name .. "_" .. name .. "_" .. i .. "_" .. hex_chars[j]);
+ hex_found = true;
+ end
+ end
+ i = i + 1;
+ until not hex_found
+ return;
+ end
+
+ for i = 1, #hex do
+ for j = 1, #hex_chars do
+ RemoveFlagPersistent(mod_flag_name .. "_" .. name .. "_" .. i .. "_" .. hex_chars[j]);
+ end
+ AddFlagPersistent(mod_flag_name .. "_" .. name .. "_" .. i .. "_" .. string.sub(hex, i, i));
+ end
+ for j = 1, #hex_chars do
+ RemoveFlagPersistent(mod_flag_name .. "_" .. name .. "_" .. #hex + 1 .. "_" .. hex_chars[j]);
+ end
+ end
+
+ ---decode hex value of named tag from disk (mod_flag_name will be prepended)
+ ---@param name string name of pseudo-variable
+ ---@return string
+ local function load_decode_hex(name)
+ -- print(string.format("load_decode '%s'", name))
+ local output = "";
+ local i = 1;
+ repeat
+ local hex_found = false;
+ for j = 1, #hex_chars do
+ if HasFlagPersistent(mod_flag_name .. "_" .. name .. "_" .. i .. "_" .. hex_chars[j]) then
+ output = output .. hex_chars[j];
+ hex_found = true;
+ break;
+ end
+ end
+ i = i + 1;
+ until not hex_found
+ return (output == "" and nil or output);
+ end
+ -- end local funcions, begin public;
+
+
+
+ ---clear named flag from disk
+ ---@param name string name of flag
+ function encoder_clear_flag(name)
+ -- print(string.format("encoder_clear '%s'", name))
+ if encoder_read_only then
+ -- print("suppressed");
+ return;
+ end
+
+ RemoveFlagPersistent(mod_flag_name .. "_" .. name);
+ end
+
+ ---add named flag to disk
+ ---@param name string name of flag
+ function encoder_add_flag(name)
+ -- print(string.format("encoder_add '%s'", name))
+ if encoder_read_only then
+ -- print("suppressed");
+ return;
+ end
+
+ AddFlagPersistent(mod_flag_name .. "_" .. name);
+ end
+
+ ---check named flag from disk
+ ---@param name string name of flag
+ function encoder_has_flag(name)
+ -- print(string.format("encoder_has '%s'", name))
+ return HasFlagPersistent(mod_flag_name .. "_" .. name);
+ end
+
+ ---decode number value of named tag from disk
+ ---@param name string name of pseudo-variable (mod_flag_name will be prepended)
+ ---@return number
+ function encoder_load_integer(name)
+ return hex_to_number(load_decode_hex(name));
+ end
+
+ ---write encoded number value of named tag to disk
+ ---@param name string name of pseudo-variable (mod_flag_name will be prepended)
+ ---@param value integer value of pseudo-variable
+ function encoder_write_integer(name, value)
+ write_encode_hex(name, number_to_hex(value));
+ end
+
+ ---clear encoded number value of named tag from disk
+ ---@param name string name of pseudo-variable (mod_flag_name will be prepended)
+ function encoder_clear_integer(name)
+ write_encode_hex(name, nil);
+ end
+ ---end function declarations, run code here;
+
+
+
+ print("=========================");
+ print("persistence: Encoder loaded.");
+ persistence_encoder_loaded=true;
+end
diff --git a/files/entity/lobby.xml b/files/entity/lobby.xml
new file mode 100644
index 0000000..eb9eeac
--- /dev/null
+++ b/files/entity/lobby.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/files/entity/lobby_collider.lua b/files/entity/lobby_collider.lua
new file mode 100644
index 0000000..4991fec
--- /dev/null
+++ b/files/entity/lobby_collider.lua
@@ -0,0 +1,10 @@
+function collision_trigger(colliding_entity_id)
+
+ GlobalsSetValue("lobby_collider_triggered", "13"); --- triggers every 10 frames, plus leeway
+
+ local _e_id = GetUpdatedEntityID();
+ GlobalsSetValue("lobby_e_id", tostring(_e_id));
+ if not EntityHasTag(_e_id, "persistence_visited") then
+ EntityAddTag(_e_id, "persistence_visited");
+ end
+end
diff --git a/files/entity/lobby_effect.lua b/files/entity/lobby_effect.lua
new file mode 100644
index 0000000..f7f4dc0
--- /dev/null
+++ b/files/entity/lobby_effect.lua
@@ -0,0 +1,10 @@
+
+if GlobalsGetValue("persistence_active", "false")=="false" then return; end
+
+local _e_id = GetUpdatedEntityID();
+if _e_id==0 then return; end
+
+local _c_id = EntityGetFirstComponentIncludingDisabled(_e_id, "GameEffectComponent");
+if _c_id==nil or _c_id==0 then return; end
+
+EntitySetComponentIsEnabled(_e_id, _c_id, GlobalsGetValue("lobby_collider_triggered", "0")~="0" or GlobalsGetValue("workshop_collider_triggered", "0")~="0");
diff --git a/files/entity/persistence_workshop.xml b/files/entity/persistence_workshop.xml
new file mode 100644
index 0000000..a331508
--- /dev/null
+++ b/files/entity/persistence_workshop.xml
@@ -0,0 +1,7 @@
+
+
+
+
\ No newline at end of file
diff --git a/files/wand_empty.xml b/files/entity/wand_empty.xml
similarity index 100%
rename from files/wand_empty.xml
rename to files/entity/wand_empty.xml
diff --git a/files/entity/workshop_collider.lua b/files/entity/workshop_collider.lua
new file mode 100644
index 0000000..ebe0a70
--- /dev/null
+++ b/files/entity/workshop_collider.lua
@@ -0,0 +1,11 @@
+function collision_trigger(colliding_entity_id)
+
+ GlobalsSetValue("workshop_collider_triggered", "13");
+
+ local _e_id = GetUpdatedEntityID();
+ GlobalsSetValue("workshop_e_id", tostring(_e_id));
+ if not EntityHasTag(_e_id, "persistence_visited") then
+ EntityAddTag(_e_id, "persistence_visited");
+ EntityAddTag(_e_id, "persistence_unpaid");
+ end
+end
diff --git a/files/entity_mgr.lua b/files/entity_mgr.lua
new file mode 100644
index 0000000..0dfeae2
--- /dev/null
+++ b/files/entity_mgr.lua
@@ -0,0 +1,128 @@
+if entity_mgr_loaded==nil then entity_mgr_loaded=false; end
+if entity_mgr_loaded==false then
+-- one time init
+ known_workshops = {};
+ lobby_e_id = 0;
+ local _lobby_entity_frame_skip=30;
+
+ local function _do_lobby_effect_check()
+ if mod_setting.enable_edit_wands_in_lobby==true then
+ local _lobby_effect_pool = EntityGetWithName("persistence_lobby_effect_entity");
+ if _lobby_effect_pool==nil or _lobby_effect_pool==0 then
+ if type(_lobby_effect_pool)=="table" then
+ for _, _e_id in ipairs(_lobby_effect_pool) do
+ EntityKill(_e_id);
+ end
+ end
+ create_lobby_effect_entity();
+ end
+ end
+ end
+
+ ---close out frame by disabling triggers
+ function OnModEndFrame()
+ if mod_disabled then return; end
+
+ local _lobby_frames = tonumber(GlobalsGetValue("lobby_collider_triggered", "0"));
+ local _workshop_frames = tonumber(GlobalsGetValue("workshop_collider_triggered", "0"));
+ local _game_frame = GameGetFrameNum();
+
+ if _lobby_frames>0 then
+ GlobalsSetValue("lobby_collider_triggered", tostring(_lobby_frames-1));
+ end ---decrement trigger per frame, re-enabled by collider entity
+
+ if _workshop_frames>0 then
+ GlobalsSetValue("workshop_collider_triggered", tostring(_workshop_frames-1));
+ end ---decrement trigger per frame, re-enabled by collider entity
+
+ if _game_frame%_lobby_entity_frame_skip==0 and _lobby_frames>1 then
+ _do_lobby_effect_check();
+ end
+ end
+
+ function LockPlayer()
+ if player_e_id==0 or not EntityGetIsAlive(player_e_id) then
+ local _e_id = EntityGetWithTag("player_unit")[1];
+ if _e_id~=nil and _e_id~=0 then
+ player_e_id = _e_id;
+ else
+ return;
+ end
+ end
+ EntitySetComponentIsEnabled(player_e_id, EntityGetFirstComponentIncludingDisabled(player_e_id, "PlatformShooterPlayerComponent") or 0, false); --- Recenters camera
+ EntitySetComponentIsEnabled(player_e_id, EntityGetFirstComponentIncludingDisabled(player_e_id, "CharacterDataComponent") or 0, false); --- Stops movement
+ EntitySetComponentIsEnabled(player_e_id, EntityGetFirstComponentIncludingDisabled(player_e_id, "DamageModelComponent") or 0, false); --- Prevents damage
+ EntitySetComponentIsEnabled(player_e_id, EntityGetFirstComponentIncludingDisabled(player_e_id, "InventoryGuiComponent") or 0, false); --- Removes inventory GUI
+ -- EntitySetComponentIsEnabled(player_e_id, EntityGetFirstComponentIncludingDisabled(player_e_id, "Inventory2Component") or 0, false); --- Disables player inventory
+ -- EntitySetComponentIsEnabled(player_e_id, EntityGetFirstComponentIncludingDisabled(player_e_id, "ControlsComponent") or 0, false); --- Disables player controls
+ end
+
+ function UnlockPlayer()
+ if player_e_id==0 or not EntityGetIsAlive(player_e_id) then
+ local _e_id = EntityGetWithTag("player_unit")[1];
+ if _e_id~=nil and _e_id~=0 then
+ player_e_id = _e_id;
+ else
+ return;
+ end
+ end
+ EntitySetComponentIsEnabled(player_e_id, EntityGetFirstComponentIncludingDisabled(player_e_id, "PlatformShooterPlayerComponent") or 0, true);
+ EntitySetComponentIsEnabled(player_e_id, EntityGetFirstComponentIncludingDisabled(player_e_id, "CharacterDataComponent") or 0, true);
+ EntitySetComponentIsEnabled(player_e_id, EntityGetFirstComponentIncludingDisabled(player_e_id, "DamageModelComponent") or 0, true);
+ EntitySetComponentIsEnabled(player_e_id, EntityGetFirstComponentIncludingDisabled(player_e_id, "InventoryGuiComponent") or 0, true);
+ -- EntitySetComponentIsEnabled(player_e_id, EntityGetFirstComponentIncludingDisabled(player_e_id, "Inventory2Component") or 0, true);
+ -- EntitySetComponentIsEnabled(player_e_id, EntityGetFirstComponentIncludingDisabled(player_e_id, "ControlsComponent") or 0, true);
+ end
+
+ function isLocked()
+ if player_e_id==0 or not EntityGetIsAlive(player_e_id) then return false; end
+ return not (ComponentGetIsEnabled(EntityGetFirstComponentIncludingDisabled(player_e_id, "PlatformShooterPlayerComponent") or 0) and
+ ComponentGetIsEnabled(EntityGetFirstComponentIncludingDisabled(player_e_id, "CharacterDataComponent") or 0) and
+ ComponentGetIsEnabled(EntityGetFirstComponentIncludingDisabled(player_e_id, "DamageModelComponent") or 0) and
+ ComponentGetIsEnabled(EntityGetFirstComponentIncludingDisabled(player_e_id, "InventoryGuiComponent") or 0) );
+ end
+
+ ---end function declarations, run code here;
+
+
+
+end
+
+
+-- TODO : Remove Move lobby to spawn mod setting?
+
+
+-- every frame
+local _frame_skip=10;
+if GameGetFrameNum()%_frame_skip==0 then -- every ten frames, for performance
+ if lobby_e_id==0 and player_e_id~=0 then
+ lobby_e_id=EntityGetWithName("persistence_lobby");
+ if lobby_e_id==0 then
+ local x_loc = GlobalsGetValue("first_spawn_x", "x");
+ local y_loc = GlobalsGetValue("first_spawn_y", "x");
+ lobby_e_id = EntityLoad(mod_dir .. "files/entity/lobby.xml", x_loc, y_loc);
+ end
+ end
+
+ local workshop_pool = EntityGetWithTag("workshop");
+ for _, workshop_e_id in ipairs(workshop_pool) do
+ if not EntityHasTag(workshop_e_id, "persistence_cloned") then
+ local workshop_hitbox_comp = EntityGetComponent(workshop_e_id, "HitboxComponent")[1];
+ if workshop_hitbox_comp~=nil then
+ local workshop_hitbox = ComponentGetMembers(workshop_hitbox_comp);
+ if workshop_hitbox~=nil then
+ print("persistence: entity_mgr.lua: cloned workshop " .. workshop_e_id);
+ local _width = workshop_hitbox["aabb_max_x"] - workshop_hitbox["aabb_min_x"];
+ local _height = workshop_hitbox["aabb_max_y"] - workshop_hitbox["aabb_min_y"];
+ local _xloc, _yloc = EntityGetFirstHitboxCenter(workshop_e_id);
+ local new_workshop_id = EntityLoad(mod_dir .. "files/entity/persistence_workshop.xml", _xloc, _yloc);
+ EntityAddComponent2(new_workshop_id, "CollisionTriggerComponent", {width=_width, height=_height, radius=1000, destroy_this_entity_when_triggered=false, required_tag="player_unit", _enabled=true});
+ EntityAddTag(workshop_e_id, "persistence_cloned");
+ if mod_setting.reusable_holy_mountain~=true then
+ EntityAddChild(workshop_e_id, new_workshop_id);
+ end
+ end
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/files/gui.lua b/files/gui.lua
index b5a992c..31277a0 100644
--- a/files/gui.lua
+++ b/files/gui.lua
@@ -1,1114 +1,126 @@
-dofile_once("mods/persistence/config.lua");
-dofile_once("mods/persistence/files/data_store.lua");
-dofile_once("mods/persistence/files/helper.lua");
-dofile_once("data/scripts/gun/procedural/wands.lua");
-dofile_once("mods/persistence/files/wand_spell_helper.lua");
-
-local gui = GuiCreate();
-local active_windows = {};
-
-local function gui_sprite(x, y, file_path)
- local cx, cy = GameGetCameraPos();
- local size_x, size_y = get_screen_size();
- GameCreateSpriteForXFrames(file_path, cx - size_x / 2 + size_x * (x / 100), cy - size_y / 2 + size_y * (y / 100), false);
-end
-
-function show_save_selector_gui()
- local delete_save_confirmation = 0;
- active_windows["save_selector"] = { true, function (get_next_id)
- GuiLayoutBeginVertical(gui, 1, 20);
- for i = 1, get_save_count() do
- GuiText(gui, 0, 0, "Save slot " .. pad_number(i, #tostring(get_save_count())) .. ":");
- if get_save_ids()[i] == nil then
- if GuiButton(gui, 20, 0, "Create new save", get_next_id()) then
- set_selected_save_id(i);
- create_new_save(i);
- hide_save_selector_gui();
- OnSaveAvailable(i);
- enable_controlls();
- end
- else
- if GuiButton(gui, 20, 0, "Load save", get_next_id()) then
- set_selected_save_id(i);
- load(i);
- hide_save_selector_gui();
- OnSaveAvailable(i);
- enable_controlls();
- end
- if delete_save_confirmation == i then
- if GuiButton(gui, 20, 0, "Press again to delete", get_next_id()) then
- delete_save_confirmation = 0;
- delete_save(i);
- end
- else
- if GuiButton(gui, 20, 0, "Delete save", get_next_id()) then
- delete_save_confirmation = i;
- end
- end
- end
- end
- if GuiButton(gui, 0, 20, "Play without this mod", get_next_id()) then
- set_selected_save_id(0);
- hide_save_selector_gui();
- enable_controlls();
- end
- GuiLayoutEnd(gui);
- end };
-end
-
-function hide_save_selector_gui()
- active_windows["save_selector"] = nil;
-end
-
-function show_money_gui()
- active_windows["money"] = { false, function(get_next_id)
- local save_id = get_selected_save_id();
- local safe_money = get_safe_money(save_id);
- local player_money = get_player_money();
-
- GuiLayoutBeginHorizontal(gui, 85, 15);
- GuiLayoutBeginVertical(gui, 0, 0);
- if safe_money < 1 then
- GuiText(gui, 0, 0, "^ 1$");
- else
- if GuiButton(gui, 0, 0, "^ 1$", get_next_id()) then
- transfer_money_to_player(save_id, 1);
- end
- end
- if safe_money < 10 then
- GuiText(gui, 0, 0, "^ 10$");
- else
- if GuiButton(gui, 0, 0, "^ 10$", get_next_id()) then
- transfer_money_to_player(save_id, 10);
- end
- end
- if safe_money < 100 then
- GuiText(gui, 0, 0, "^ 100$");
- else
- if GuiButton(gui, 0, 0, "^ 100$", get_next_id()) then
- transfer_money_to_player(save_id, 100);
- end
- end
- if safe_money < 1000 then
- GuiText(gui, 0, 0, "^ 1000$");
- else
- if GuiButton(gui, 0, 0, "^ 1000$", get_next_id()) then
- transfer_money_to_player(save_id, 1000);
- end
- end
- if GuiButton(gui, 0, 0, "^ ALL", get_next_id()) then
- transfer_money_to_player(save_id, safe_money);
- end
- GuiLayoutEnd(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutBeginVertical(gui, 0, 0);
- if GuiButton(gui, 0, 0, "v ALL", get_next_id()) then
- transfer_money_to_safe(save_id, player_money);
- end
- if player_money < 1000 then
- GuiText(gui, 0, 0, "v 1000$");
- else
- if GuiButton(gui, 0, 0, "v 1000$", get_next_id()) then
- transfer_money_to_safe(save_id, 1000);
- end
- end
- if player_money < 100 then
- GuiText(gui, 0, 0, "v 100$");
- else
- if GuiButton(gui, 0, 0, "v 100$", get_next_id()) then
- transfer_money_to_safe(save_id, 100);
- end
- end
- if player_money < 10 then
- GuiText(gui, 0, 0, "v 10$");
- else
- if GuiButton(gui, 0, 0, "v 10$", get_next_id()) then
- transfer_money_to_safe(save_id, 10);
- end
- end
- if player_money < 1 then
- GuiText(gui, 0, 0, "v 1$");
- else
- if GuiButton(gui, 0, 0, "v 1$", get_next_id()) then
- transfer_money_to_safe(save_id, 1);
- end
- end
- GuiLayoutEnd(gui);
- GuiLayoutEnd(gui);
- GuiLayoutBeginHorizontal(gui, 86, 31);
- GuiText(gui, 0, 0, " $" .. tostring(safe_money));
- GuiLayoutEnd(gui);
- end };
-end
-
-function hide_money_gui()
- active_windows["money"] = nil;
-end
-
-function show_teleport_gui()
- local teleport_confirmation = false;
- active_windows["teleport"] = { false, function(get_next_id)
- GuiLayoutBeginHorizontal(gui, 45, 1);
- if teleport_confirmation then
- if GuiButton(gui, 0, 0, "Press again to teleport", get_next_id()) then
- teleport_back_to_lobby();
- end
- else
- if GuiButton(gui, 0, 0, "Teleport back up", get_next_id()) then
- teleport_confirmation = true;
- end
- end
- GuiLayoutEnd(gui);
- end };
-end
-
-function hide_teleport_gui()
- active_windows["teleport"] = nil;
+if persistence_active==false then return; end
+if persistence_gui_loaded~=true then
+ -- once, on load
+ dofile_once("data/scripts/debug/keycodes.lua");
+ dofile_once(mod_dir .. "files/data_store.lua");
+ dofile_once(mod_dir .. "files/gui_subfunc.lua");
+
+ gui = GuiCreate();
+ active_windows = {};
+ fourslot_table = {};
+ fourslot_confirmation = 0;
+ spell_list_confirmation = 0;
+ small_text_scale = 0.9;
+ spell_tooltip_id = "";
+ _right_panel_id = 0;
+
+ window_open=false;
+
+ function __nil(...) return; end
+ function __layer(n) return 1000 - ((n+1) * 50); end
+
+ dofile_once(mod_dir .. "files/gui/fourslot.lua");
+ dofile_once(mod_dir .. "files/gui/modify_wand.lua");
+ dofile_once(mod_dir .. "files/gui/money.lua");
+ dofile_once(mod_dir .. "files/gui/persistence.lua");
+ dofile_once(mod_dir .. "files/gui/scan_nearby_entities.lua");
+ dofile_once(mod_dir .. "files/gui/spell_list.lua");
+ dofile_once(mod_dir .. "files/gui/spell_loadout.lua");
+ dofile_once(mod_dir .. "files/gui/spell_tooltip.lua");
+ dofile_once(mod_dir .. "files/gui/teleport.lua");
+ dofile_once(mod_dir .. "files/gui/wand_template.lua");
+
+ ---Close all open windows
+ function close_open_windows()
+ -- close_profile_select();
+ close_wands();
+ close_money();
+ close_wands();
+ close_purchase_spells();
+ close_inventory_spells();
+ close_modify_wand();
+ -- close_wand_template();
+ close_scan_nearby_entities();
+ -- close_spell_loadouts();
+ right_panel_picker(0);
+ -- close_persistence_menu();
+ -- close_spell_tooltip();
+ end
+ ---end function declarations, run code here;
+
+ print("=========================");
+ print("persistence: GUI loaded.");
+ persistence_gui_loaded=true;
+end
+-- every frame;
+if selected_profile_id~=DISABLE_PROFILE_ID then
+ _in_lobby = GlobalsGetValue("lobby_collider_triggered", "0")~="0";
+ _in_workshop = GlobalsGetValue("workshop_collider_triggered", "0")~="0";
+ _allow_teleport = _in_workshop and mod_setting.enable_teleport_back_up==true;
+ _allow_workshop = _in_workshop and mod_setting.enable_menu_in_holy_mountain==true;
+ _in_persistence_area = _in_lobby or _allow_workshop;
+ _persistence_available = _in_persistence_area or mod_setting.global_persistence==true;
+
+ data_store_everyframe();
+ if spell_tooltip_id=="" then close_spell_tooltip(); end
+ spell_tooltip_id="";
+
+ if loaded_profile_id>0 then
+ ---profile loaded, proceed as normal
+ if profile_open then close_profile_select(); end
+
+ if isLocked() then UnlockPlayer(); end
+
+ present_scan_nearby_entities();
+
+ if InputIsKeyJustDown(Key_ESCAPE) then
+ close_open_windows();
+ end
+
+ if _allow_teleport then
+ present_teleport();
+ else
+ close_teleport();
+ end
+ if _persistence_available then
+ present_persistence_menu();
+ else
+ close_persistence_menu();
+ end
+ elseif selected_profile_id==DISABLE_PROFILE_ID then
+ if isLocked() then UnlockPlayer(); end
+ close_open_windows();
+ close_profile_select();
+ else
+ if not isLocked() then LockPlayer(); end
+ present_profile_select();
+ end
+else
+ if isLocked() then UnlockPlayer(); end
+ close_open_windows();
+ close_profile_select();
+end
+
+window_open = profile_open or money_open or wands_open or inventory_spells_open or modify_wand_open;
+-- if window_open and spell_tooltip_id~="" then show_spell_tooltip_gui(); end
+
+if gui~=nil and active_windows~=nil and EntityGetIsAlive(player_e_id) then
+ GuiStartFrame(gui);
+ local start_gui_id = 319585;
+ if window_open then
+ if not isLocked() then LockPlayer(); end
+ GuiZSetForNextWidget(gui, 1000);
+ GuiImage(gui, start_gui_id - 1, 0, 0, mod_dir .. "files/img/gui_darken.png", 1, 1, 1, 0);
+ end
+ for name, window in pairs(active_windows) do
+ if window~=nil then
+ local gui_id = start_gui_id + simple_string_hash(name);
+ window(function()
+ gui_id = gui_id + 1;
+ return gui_id;
+ end);
+ end
+ end
+else
+ if isLocked() then UnlockPlayer(); end
end
-
-local research_wands_open = false;
-function show_research_wands_gui()
- research_wands_open = true;
- local wand_entity_ids = get_all_wands();
- local wands = {};
-
- for pos, entity_id in pairs(wand_entity_ids) do
- wands[pos] = {
- ["entity_id"] = entity_id,
- ["wand_data"] = read_wand(entity_id)
- };
- end
-
- active_windows["research_wands"] = { true, function(get_next_id)
- local player_money = get_player_money();
- GuiLayoutBeginHorizontal(gui, 30, 30);
- GuiLayoutBeginVertical(gui, 0, 0);
- for i = 1, 4 do
- GuiText(gui, 0, 0, "Inventory Slot " .. tostring(i) .. ":");
- end
- GuiLayoutEnd(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutBeginVertical(gui, 0, 0);
- for i = 0, 3 do
- if wands[i] ~= nil then
- local price = research_wand_price(get_selected_save_id(), wands[i].entity_id);
- local is_new = research_wand_is_new(get_selected_save_id(), wands[i].entity_id);
- if is_new then
- if price > player_money then
- GuiText(gui, 0, 0, tostring(price) .. "$");
- else
- if #wands[i].wand_data.spells > 0 then
- if GuiButton(gui, 0, 0, tostring(price) .. "$", get_next_id()) then
- research_wand(get_selected_save_id(), wands[i].entity_id);
- wands[i] = nil;
- end
- else
- if GuiButton(gui, 0, 0, tostring(price) .. "$", get_next_id()) then
- research_wand(get_selected_save_id(), wands[i].entity_id);
- wands[i] = nil;
- end
- end
- end
- else
- GuiText(gui, 0, 0, "0$");
- end
- else
- GuiText(gui, 0, 0, " ");
- end
- end
- GuiLayoutEnd(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutBeginVertical(gui, 0, 0);
- for i = 0, 3 do
- if wands[i] ~= nil then
- local price = research_wand_price(get_selected_save_id(), wands[i].entity_id);
- local is_new = research_wand_is_new(get_selected_save_id(), wands[i].entity_id);
- if is_new then
- if price > player_money then
- GuiText(gui, 0, 0, "You can't afford that");
- else
- if #wands[i].wand_data.spells > 0 then
- GuiText(gui, 0, 0, "WARNING: The spells on this wand will be lost");
- else
- GuiText(gui, 0, 0, " ");
- end
- end
- else
- GuiText(gui, 0, 0, "This wand does not have anything new to research");
- end
- else
- GuiText(gui, 0, 0, " ");
- end
- end
- GuiLayoutEnd(gui);
- GuiLayoutEnd(gui);
- end };
-end
-
-function hide_research_wands_gui()
- research_wands_open = false;
- active_windows["research_wands"] = nil;
-end
-
-local research_spells_open = false;
-function show_research_spells_gui()
- research_spells_open = true;
- local spell_entity_ids = get_all_spells();
- local researched_spells = get_spells(get_selected_save_id());
- local spell_data_temp = {};
- local spell_data = {};
-
- for i = 1, #spell_entity_ids do
- local action_id = read_spell(spell_entity_ids[i]);
- if action_id ~= nil then
- if researched_spells[action_id] == nil then
- spell_data_temp[action_id] = spell_entity_ids[i];
- end
- end
- end
- for i = 1, #actions do
- local entity_id = spell_data_temp[actions[i].id];
- if entity_id ~= nil then
- table.insert(spell_data, {
- ["entity_id"] = entity_id,
- ["id"] = actions[i].id,
- ["name"] = GameTextGetTranslatedOrNot(actions[i].name),
- ["price"] = research_spell_price(entity_id)
- });
- end
- end
- table.sort(spell_data, function(a, b) return a.name < b.name end);
-
- active_windows["research_spells"] = { true, function(get_next_id)
- if #spell_data > 0 then
- local player_money = get_player_money();
- GuiLayoutBeginHorizontal(gui, 40, 15);
- GuiLayoutBeginVertical(gui, 0, 0);
- for _, value in ipairs(spell_data) do
- if player_money < value.price then
- GuiText(gui, 0, 0, tostring(value.price) .. "$");
- else
- if GuiButton(gui, 0, 0, tostring(value.price) .. "$", get_next_id()) then
- research_spell(get_selected_save_id(), value.entity_id);
- hide_research_spells_gui();
- show_research_spells_gui();
- end
- end
- end
- GuiLayoutEnd(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutBeginVertical(gui, 0, 0);
- for _, value in ipairs(spell_data) do
- GuiText(gui, 0, 0, value.name);
- end
- GuiLayoutEnd(gui);
- GuiLayoutEnd(gui);
- else
- GuiLayoutBeginHorizontal(gui, 40, 30);
- GuiText(gui, 0, 0, "No new spells to research");
- GuiLayoutEnd(gui);
- end
- end };
-end
-
-function hide_research_spells_gui()
- research_spells_open = false;
- active_windows["research_spells"] = nil;
-end
-
-local buy_wands_open = false;
-function show_buy_wands_gui()
- buy_wands_open = true;
- local save_id = get_selected_save_id();
- if can_create_wand(save_id) then
- local window_nr = 0;
- local spells_per_cast = get_spells_per_cast(save_id);
- local cast_delay_min = get_cast_delay_min(save_id);
- local cast_delay_max = get_cast_delay_max(save_id);
- local recharge_time_min = get_recharge_time_min(save_id);
- local recharge_time_max = get_recharge_time_max(save_id);
- local mana_max = get_mana_max(save_id);
- local mana_charge_speed = get_mana_charge_speed(save_id);
- local capacity = get_capacity(save_id);
- local spread_min = get_spread_min(save_id);
- local spread_max = get_spread_max(save_id);
- local wand_types = get_wand_types(save_id);
- local always_cast_spells = get_always_cast_spells(save_id);
- local wand_data_selected = {
- ["shuffle"] = true,
- ["spells_per_cast"] = spells_per_cast_min,
- ["cast_delay"] = math.floor((cast_delay_min + cast_delay_max)/2),
- ["recharge_time"] = math.floor((recharge_time_min + recharge_time_max)/2),
- ["mana_max"] = mana_max_min,
- ["mana_charge_speed"] = mana_charge_speed_min,
- ["capacity"] = capacity_min,
- ["spread"] = spread_min,
- ["always_cast_spells"] = {},
- ["wand_type"] = "default_1";
- };
- local delete_template_confirmation = 0;
-
- local spells_page_number = 1;
- local spell_data = {};
-
- for i = 1, #actions do
- if always_cast_spells[actions[i].id] ~= nil then
- table.insert(spell_data, {
- ["id"] = actions[i].id,
- ["name"] = GameTextGetTranslatedOrNot(actions[i].name),
- ["selected"] = false
- });
- end
- end
-
- local function toggle_select_spell(action_id)
- local selected = false;
- for i = 1, #spell_data do
- if spell_data[i].id == action_id then
- selected = not spell_data[i].selected;
- spell_data[i].selected = selected;
- break;
- end
- end
- for i = 1, #wand_data_selected["always_cast_spells"] do
- if wand_data_selected["always_cast_spells"][i] == action_id then
- table.remove(wand_data_selected["always_cast_spells"], i);
- break;
- end
- end
- if selected then
- table.insert(wand_data_selected["always_cast_spells"], action_id);
- end
- end
-
- table.sort(spell_data, function(a, b) return a.name < b.name end);
- local spell_columns = split_array(spell_data, 20);
-
- local wand_types_page_number = 1;
- local wand_type_list = {};
-
- for wand_type, _ in pairs(wand_types) do
- table.insert(wand_type_list, {
- ["wand_type"] = wand_type,
- ["sprite_file"] = wand_type_to_sprite_file(wand_type)
- });
- end
-
- table.sort(wand_type_list, function(a, b) return a.wand_type < b.wand_type end);
- local wand_type_columns = split_array(wand_type_list, 5);
-
- active_windows["buy_wands"] = { true, function(get_next_id)
- local player_money = get_player_money();
- local price = create_wand_price(wand_data_selected);
- if window_nr == 0 then
- GuiLayoutBeginHorizontal(gui, 20, 15);
- GuiLayoutBeginVertical(gui, 0, 0);
- GuiText(gui, 0, 0, "$inventory_shuffle");
- GuiText(gui, 0, 0, "$inventory_actionspercast");
- GuiText(gui, 0, 0, "$inventory_castdelay");
- GuiText(gui, 0, 0, "$inventory_rechargetime");
- GuiText(gui, 0, 0, "$inventory_manamax");
- GuiText(gui, 0, 0, "$inventory_manachargespeed");
- GuiText(gui, 0, 0, "$inventory_capacity");
- GuiText(gui, 0, 0, "$inventory_spread");
- GuiText(gui, 0, 0, "$inventory_alwayscasts");
- GuiText(gui, 0, 0, "Wand design");
- GuiLayoutEnd(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutBeginVertical(gui, 0, 0);
- GuiText(gui, 0, 0, " ");
- GuiText(gui, 0, 0, " ");
- if wand_data_selected["cast_delay"] - 60 >= cast_delay_min then
- if GuiButton(gui, 0, 0, "<<<", get_next_id()) then
- wand_data_selected["cast_delay"] = wand_data_selected["cast_delay"] - 60;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- if wand_data_selected["recharge_time"] - 60 >= recharge_time_min then
- if GuiButton(gui, 0, 0, "<<<", get_next_id()) then
- wand_data_selected["recharge_time"] = wand_data_selected["recharge_time"] - 60;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- if wand_data_selected["mana_max"] - 100 >= mana_max_min then
- if GuiButton(gui, 0, 0, "<<<", get_next_id()) then
- wand_data_selected["mana_max"] = wand_data_selected["mana_max"] - 100;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- if wand_data_selected["mana_charge_speed"] - 100 >= mana_charge_speed_min then
- if GuiButton(gui, 0, 0, "<<<", get_next_id()) then
- wand_data_selected["mana_charge_speed"] = wand_data_selected["mana_charge_speed"] - 100;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- GuiText(gui, 0, 0, " ");
- if wand_data_selected["spread"] - 10 >= spread_min then
- if GuiButton(gui, 0, 0, "<<<<", get_next_id()) then
- wand_data_selected["spread"] = wand_data_selected["spread"] - 10;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- GuiText(gui, 0, 0, " ");
- GuiLayoutEnd(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutBeginVertical(gui, 0, 0);
- GuiText(gui, 0, 0, " ");
- GuiText(gui, 0, 0, " ");
- if wand_data_selected["cast_delay"] - 6 >= cast_delay_min then
- if GuiButton(gui, 0, 0, "<<", get_next_id()) then
- wand_data_selected["cast_delay"] = wand_data_selected["cast_delay"] - 6;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- if wand_data_selected["recharge_time"] - 6 >= recharge_time_min then
- if GuiButton(gui, 0, 0, "<<", get_next_id()) then
- wand_data_selected["recharge_time"] = wand_data_selected["recharge_time"] - 6;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- if wand_data_selected["mana_max"] - 10 >= mana_max_min then
- if GuiButton(gui, 0, 0, "<<", get_next_id()) then
- wand_data_selected["mana_max"] = wand_data_selected["mana_max"] - 10;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- if wand_data_selected["mana_charge_speed"] - 10 >= mana_charge_speed_min then
- if GuiButton(gui, 0, 0, "<<", get_next_id()) then
- wand_data_selected["mana_charge_speed"] = wand_data_selected["mana_charge_speed"] - 10;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- if wand_data_selected["capacity"] - 10 >= capacity_min then
- if GuiButton(gui, 0, 0, "<<", get_next_id()) then
- wand_data_selected["capacity"] = wand_data_selected["capacity"] - 10;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- if wand_data_selected["spread"] - 1 >= spread_min then
- if GuiButton(gui, 0, 0, "<<", get_next_id()) then
- wand_data_selected["spread"] = wand_data_selected["spread"] - 1;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- GuiText(gui, 0, 0, " ");
- GuiLayoutEnd(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutBeginVertical(gui, 0, 0);
- if wand_data_selected["shuffle"] == true then
- if GuiButton(gui, 0, 0, "<", get_next_id()) then
- wand_data_selected["shuffle"] = false;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- if wand_data_selected["spells_per_cast"] > spells_per_cast_min then
- if GuiButton(gui, 0, 0, "<", get_next_id()) then
- wand_data_selected["spells_per_cast"] = wand_data_selected["spells_per_cast"] - 1;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- if wand_data_selected["cast_delay"] > cast_delay_min then
- if GuiButton(gui, 0, 0, "<", get_next_id()) then
- wand_data_selected["cast_delay"] = wand_data_selected["cast_delay"] - 1;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- if wand_data_selected["recharge_time"] > recharge_time_min then
- if GuiButton(gui, 0, 0, "<", get_next_id()) then
- wand_data_selected["recharge_time"] = wand_data_selected["recharge_time"] - 1;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- if wand_data_selected["mana_max"] > mana_max_min then
- if GuiButton(gui, 0, 0, "<", get_next_id()) then
- wand_data_selected["mana_max"] = wand_data_selected["mana_max"] - 1;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- if wand_data_selected["mana_charge_speed"] > mana_charge_speed_min then
- if GuiButton(gui, 0, 0, "<", get_next_id()) then
- wand_data_selected["mana_charge_speed"] = wand_data_selected["mana_charge_speed"] - 1;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- if wand_data_selected["capacity"] > capacity_min then
- if GuiButton(gui, 0, 0, "<", get_next_id()) then
- wand_data_selected["capacity"] = wand_data_selected["capacity"] - 1;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- if wand_data_selected["spread"] - 0.1 >= spread_min then
- if GuiButton(gui, 0, 0, "<", get_next_id()) then
- wand_data_selected["spread"] = wand_data_selected["spread"] - 0.1;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- GuiText(gui, 0, 0, " ");
- GuiLayoutEnd(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutBeginVertical(gui, 0, 0);
- GuiText(gui, 0, 0, wand_data_selected["shuffle"] and "$menu_yes" or "$menu_no");
- GuiText(gui, 0, 0, tostring(wand_data_selected["spells_per_cast"]));
- GuiText(gui, 0, 0, tostring(math.floor((wand_data_selected["cast_delay"] / 60) * 100 + 0.5) / 100));
- GuiText(gui, 0, 0, tostring(math.floor((wand_data_selected["recharge_time"] / 60) * 100 + 0.5) / 100));
- GuiText(gui, 0, 0, tostring(wand_data_selected["mana_max"]));
- GuiText(gui, 0, 0, tostring(wand_data_selected["mana_charge_speed"]));
- GuiText(gui, 0, 0, tostring(wand_data_selected["capacity"]));
- GuiText(gui, 0, 0, tostring(math.floor(wand_data_selected["spread"] * 10 + 0.5) / 10));
- if GuiButton(gui, 0, 0, "Select", get_next_id()) then
- window_nr = 1;
- end
- if GuiButton(gui, 0, 0, "Select", get_next_id()) then
- window_nr = 2;
- end
- GuiLayoutEnd(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutBeginVertical(gui, 0, 0);
- if wand_data_selected["shuffle"] == false then
- if GuiButton(gui, 0, 0, ">", get_next_id()) then
- wand_data_selected["shuffle"] = true;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- if wand_data_selected["spells_per_cast"] < spells_per_cast then
- if GuiButton(gui, 0, 0, ">", get_next_id()) then
- wand_data_selected["spells_per_cast"] = wand_data_selected["spells_per_cast"] + 1;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- if wand_data_selected["cast_delay"] < cast_delay_max then
- if GuiButton(gui, 0, 0, ">", get_next_id()) then
- wand_data_selected["cast_delay"] = wand_data_selected["cast_delay"] + 1;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- if wand_data_selected["recharge_time"] < recharge_time_max then
- if GuiButton(gui, 0, 0, ">", get_next_id()) then
- wand_data_selected["recharge_time"] = wand_data_selected["recharge_time"] + 1;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- if wand_data_selected["mana_max"] < mana_max then
- if GuiButton(gui, 0, 0, ">", get_next_id()) then
- wand_data_selected["mana_max"] = wand_data_selected["mana_max"] + 1;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- if wand_data_selected["mana_charge_speed"] < mana_charge_speed then
- if GuiButton(gui, 0, 0, ">", get_next_id()) then
- wand_data_selected["mana_charge_speed"] = wand_data_selected["mana_charge_speed"] + 1;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- if wand_data_selected["capacity"] < capacity then
- if GuiButton(gui, 0, 0, ">", get_next_id()) then
- wand_data_selected["capacity"] = wand_data_selected["capacity"] + 1;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- if wand_data_selected["spread"] + 0.1 <= spread_max then
- if GuiButton(gui, 0, 0, ">", get_next_id()) then
- wand_data_selected["spread"] = wand_data_selected["spread"] + 0.1;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- GuiText(gui, 0, 0, " ");
- GuiLayoutEnd(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutBeginVertical(gui, 0, 0);
- GuiText(gui, 0, 0, " ");
- GuiText(gui, 0, 0, " ");
- if wand_data_selected["cast_delay"] + 6 <= cast_delay_max then
- if GuiButton(gui, 0, 0, ">>", get_next_id()) then
- wand_data_selected["cast_delay"] = wand_data_selected["cast_delay"] + 6;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- if wand_data_selected["recharge_time"] + 6 <= recharge_time_max then
- if GuiButton(gui, 0, 0, ">>", get_next_id()) then
- wand_data_selected["recharge_time"] = wand_data_selected["recharge_time"] + 6;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- if wand_data_selected["mana_max"] + 10 <= mana_max then
- if GuiButton(gui, 0, 0, ">>", get_next_id()) then
- wand_data_selected["mana_max"] = wand_data_selected["mana_max"] + 10;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- if wand_data_selected["mana_charge_speed"] + 10 <= mana_charge_speed then
- if GuiButton(gui, 0, 0, ">>", get_next_id()) then
- wand_data_selected["mana_charge_speed"] = wand_data_selected["mana_charge_speed"] + 10;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- if wand_data_selected["capacity"] + 10 <= capacity then
- if GuiButton(gui, 0, 0, ">>", get_next_id()) then
- wand_data_selected["capacity"] = wand_data_selected["capacity"] + 10;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- if wand_data_selected["spread"] + 1 <= spread_max then
- if GuiButton(gui, 0, 0, ">>", get_next_id()) then
- wand_data_selected["spread"] = wand_data_selected["spread"] + 1;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- GuiText(gui, 0, 0, " ");
- GuiLayoutEnd(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutBeginVertical(gui, 0, 0);
- GuiText(gui, 0, 0, " ");
- GuiText(gui, 0, 0, " ");
- if wand_data_selected["cast_delay"] + 60 <= cast_delay_max then
- if GuiButton(gui, 0, 0, ">>>", get_next_id()) then
- wand_data_selected["cast_delay"] = wand_data_selected["cast_delay"] + 60;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- if wand_data_selected["recharge_time"] + 60 <= recharge_time_max then
- if GuiButton(gui, 0, 0, ">>>", get_next_id()) then
- wand_data_selected["recharge_time"] = wand_data_selected["recharge_time"] + 60;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- if wand_data_selected["mana_max"] + 100 <= mana_max then
- if GuiButton(gui, 0, 0, ">>>", get_next_id()) then
- wand_data_selected["mana_max"] = wand_data_selected["mana_max"] + 100;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- if wand_data_selected["mana_charge_speed"] + 100 <= mana_charge_speed then
- if GuiButton(gui, 0, 0, ">>>", get_next_id()) then
- wand_data_selected["mana_charge_speed"] = wand_data_selected["mana_charge_speed"] + 100;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- GuiText(gui, 0, 0, " ");
- if wand_data_selected["spread"] + 10 <= spread_max then
- if GuiButton(gui, 0, 0, ">>>", get_next_id()) then
- wand_data_selected["spread"] = wand_data_selected["spread"] + 10;
- end
- else
- GuiButton(gui, 0, 0, " ", get_next_id());
- end
- GuiText(gui, 0, 0, " ");
- GuiLayoutEnd(gui);
- GuiLayoutEnd(gui);
- gui_sprite(22, 48, wand_type_to_sprite_file(wand_data_selected["wand_type"]));
-
- GuiLayoutBeginVertical(gui, 80, 50);
- for i = 1, get_template_count() do
- GuiText(gui, 0, 0, "Wand template slot " .. pad_number(i, #tostring(get_template_count())) .. ":");
- if get_template(save_id, i) == nil then
- if GuiButton(gui, 40, 0, "Save template", get_next_id()) then
- set_template(save_id, i, wand_data_selected);
- end
- else
- if GuiButton(gui, 40, 0, "Load template", get_next_id()) then
- wand_data_selected = get_template(save_id, i);
- end
- if delete_template_confirmation == i then
- if GuiButton(gui, 40, 0, "Press again to delete", get_next_id()) then
- delete_template_confirmation = 0;
- delete_template(save_id, i);
- end
- else
- if GuiButton(gui, 40, 0, "Delete template", get_next_id()) then
- delete_template_confirmation = i;
- end
- end
- end
- end
- GuiLayoutEnd(gui);
- elseif window_nr == 1 then
- if spell_columns[spells_page_number * 2 - 1] ~= nil then
- GuiLayoutBeginHorizontal(gui, 30, 15);
- GuiLayoutBeginVertical(gui, 0, 0);
- for _, value in ipairs(spell_columns[spells_page_number * 2 - 1]) do
- if GuiButton(gui, 0, 0, "[" .. (value.selected and "x" or " ") .. "]", get_next_id()) then
- toggle_select_spell(value.id);
- end
- end
- GuiLayoutEnd(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutBeginVertical(gui, 0, 0);
- for _, value in ipairs(spell_columns[spells_page_number * 2 - 1]) do
- GuiText(gui, 0, 0, value.name);
- end
- GuiLayoutEnd(gui);
- GuiLayoutEnd(gui);
- end
- if spell_columns[spells_page_number * 2] ~= nil then
- GuiLayoutBeginHorizontal(gui, 60, 15);
- GuiLayoutBeginVertical(gui, 0, 0);
- for _, value in ipairs(spell_columns[spells_page_number * 2]) do
- if GuiButton(gui, 0, 0, "[" .. (value.selected and "x" or " ") .. "]", get_next_id()) then
- toggle_select_spell(value.id);
- end
- end
- GuiLayoutEnd(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutBeginVertical(gui, 0, 0);
- for _, value in ipairs(spell_columns[spells_page_number * 2]) do
- GuiText(gui, 0, 0, value.name);
- end
- GuiLayoutEnd(gui);
- GuiLayoutEnd(gui);
- end
- if spells_page_number > 1 then
- GuiLayoutBeginHorizontal(gui, 48, 95);
- if GuiButton(gui, 0, 0, "<<", get_next_id()) then
- spells_page_number = spells_page_number - 1;
- end
- GuiLayoutEnd(gui);
- end
- GuiLayoutBeginHorizontal(gui, 50, 95);
- GuiText(gui, 0, 0, tostring(spells_page_number));
- GuiLayoutEnd(gui);
- if spells_page_number < math.ceil(#spell_columns / 2) then
- GuiLayoutBeginHorizontal(gui, 52, 95);
- if GuiButton(gui, 0, 0, ">>", get_next_id()) then
- spells_page_number = spells_page_number + 1;
- end
- GuiLayoutEnd(gui);
- end
- elseif window_nr == 2 then
- if wand_type_columns[wand_types_page_number * 2 - 1] ~= nil then
- for i, value in ipairs(wand_type_columns[wand_types_page_number * 2 - 1]) do
- GuiLayoutBeginHorizontal(gui, 20, 16 + i * 10);
- if GuiButton(gui, 0, 0, "Select", get_next_id()) then
- wand_data_selected["wand_type"] = value.wand_type;
- window_nr = 0;
- wand_types_page_number = 1;
- end
- GuiLayoutEnd(gui);
- end
- for i, value in ipairs(wand_type_columns[wand_types_page_number * 2 - 1]) do
- gui_sprite(25, 15 + i * 10, value.sprite_file);
- end
- end
- if wand_type_columns[wand_types_page_number * 2] ~= nil then
- for i, value in ipairs(wand_type_columns[wand_types_page_number * 2]) do
- GuiLayoutBeginHorizontal(gui, 60, 16 + i * 10);
- if GuiButton(gui, 0, 0, "Select", get_next_id()) then
- wand_data_selected["wand_type"] = value.wand_type;
- window_nr = 0;
- wand_types_page_number = 1;
- end
- GuiLayoutEnd(gui);
- end
- for i, value in ipairs(wand_type_columns[wand_types_page_number * 2]) do
- gui_sprite(65, 15 + i * 10, value.sprite_file);
- end
- end
- if wand_types_page_number > 1 then
- GuiLayoutBeginHorizontal(gui, 48, 95);
- if GuiButton(gui, 0, 0, "<<", get_next_id()) then
- wand_types_page_number = wand_types_page_number - 1;
- end
- GuiLayoutEnd(gui);
- end
- GuiLayoutBeginHorizontal(gui, 50, 95);
- GuiText(gui, 0, 0, tostring(wand_types_page_number));
- GuiLayoutEnd(gui);
- if wand_types_page_number < math.ceil(#wand_type_columns / 2) then
- GuiLayoutBeginHorizontal(gui, 52, 95);
- if GuiButton(gui, 0, 0, ">>", get_next_id()) then
- wand_types_page_number = wand_types_page_number + 1;
- end
- GuiLayoutEnd(gui);
- end
- end
- if window_nr ~= 0 then
- GuiLayoutBeginHorizontal(gui, 15, 15);
- if GuiButton(gui, 0, 0, "$menu_return", get_next_id()) then
- window_nr = 0;
- spells_page_number = 1;
- wand_types_page_number = 1;
- end
- GuiLayoutEnd(gui);
- end
-
- GuiLayoutBeginHorizontal(gui, 20, 95);
- if player_money < price then
- GuiText(gui, 0, 0, tostring(price) .. "$ You can't afford that");
- else
- if GuiButton(gui, 0, 0, tostring(price) .. "$ Buy", get_next_id()) then
- create_wand(wand_data_selected);
- end
- end
- GuiLayoutEnd(gui);
- end };
- else
- active_windows["buy_wands"] = { true, function(get_next_id)
- GuiLayoutBeginHorizontal(gui, 40, 30);
- GuiText(gui, 0, 0, "You don't have enough research to create a wand");
- GuiLayoutEnd(gui);
- end };
- end
-end
-
-function hide_buy_wands_gui()
- buy_wands_open = false;
- active_windows["buy_wands"] = nil;
-end
-
-local buy_spells_open = false;
-function show_buy_spells_gui()
- buy_spells_open = true;
- local page_number = 1;
- local spells = get_spells(get_selected_save_id());
- local spell_data = {};
-
- for i = 1, #actions do
- if spells[actions[i].id] ~= nil then
- table.insert(spell_data, {
- ["id"] = actions[i].id,
- ["name"] = GameTextGetTranslatedOrNot(actions[i].name),
- ["price"] = create_spell_price(actions[i].id)
- });
- end
- end
-
- table.sort(spell_data, function(a, b) return a.name < b.name end);
- local columns = split_array(spell_data, 20);
-
- active_windows["buy_spells"] = { true, function(get_next_id)
- local player_money = get_player_money();
- if columns[page_number * 2 - 1] ~= nil then
- GuiLayoutBeginHorizontal(gui, 30, 15);
- GuiLayoutBeginVertical(gui, 0, 0);
- for _, value in ipairs(columns[page_number * 2 - 1]) do
- if player_money < value.price then
- GuiText(gui, 0, 0, tostring(value.price) .. "$");
- else
- if GuiButton(gui, 0, 0, tostring(value.price) .. "$", get_next_id()) then
- create_spell(value.id);
- end
- end
- end
- GuiLayoutEnd(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutBeginVertical(gui, 0, 0);
- for _, value in ipairs(columns[page_number * 2 - 1]) do
- GuiText(gui, 0, 0, value.name);
- end
- GuiLayoutEnd(gui);
- GuiLayoutEnd(gui);
- end
- if columns[page_number * 2] ~= nil then
- GuiLayoutBeginHorizontal(gui, 60, 15);
- GuiLayoutBeginVertical(gui, 0, 0);
- for _, value in ipairs(columns[page_number * 2]) do
- if player_money < value.price then
- GuiText(gui, 0, 0, tostring(value.price) .. "$");
- else
- if GuiButton(gui, 0, 0, tostring(value.price) .. "$", get_next_id()) then
- create_spell(value.id);
- end
- end
- end
- GuiLayoutEnd(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutAddHorizontalSpacing(gui);
- GuiLayoutBeginVertical(gui, 0, 0);
- for _, value in ipairs(columns[page_number * 2]) do
- GuiText(gui, 0, 0, value.name);
- end
- GuiLayoutEnd(gui);
- GuiLayoutEnd(gui);
- end
- if page_number > 1 then
- GuiLayoutBeginHorizontal(gui, 48, 95);
- if GuiButton(gui, 0, 0, "<<", get_next_id()) then
- page_number = page_number - 1;
- end
- GuiLayoutEnd(gui);
- end
- GuiLayoutBeginHorizontal(gui, 50, 95);
- GuiText(gui, 0, 0, tostring(page_number));
- GuiLayoutEnd(gui);
- if page_number < math.ceil(#columns / 2) then
- GuiLayoutBeginHorizontal(gui, 52, 95);
- if GuiButton(gui, 0, 0, ">>", get_next_id()) then
- page_number = page_number + 1;
- end
- GuiLayoutEnd(gui);
- end
- end };
-end
-
-function hide_buy_spells_gui()
- buy_spells_open = false;
- active_windows["buy_spells"] = nil;
-end
-
-function show_menu_gui()
- hide_research_wands_gui();
- hide_research_spells_gui();
- hide_buy_wands_gui();
- hide_buy_spells_gui();
- active_windows["menu"] = { false, function(get_next_id)
- GuiLayoutBeginVertical(gui, 1, 30);
- if GuiButton(gui, research_wands_open and 10 or 0, 0, "Research Wands", get_next_id()) then
- hide_research_spells_gui();
- hide_buy_wands_gui();
- hide_buy_spells_gui();
- if research_wands_open then
- hide_research_wands_gui();
- else
- show_research_wands_gui();
- end
- end
- if GuiButton(gui, research_spells_open and 10 or 0, 0, "Research Spells", get_next_id()) then
- hide_research_wands_gui();
- hide_buy_wands_gui();
- hide_buy_spells_gui();
- if research_spells_open then
- hide_research_spells_gui();
- else
- show_research_spells_gui();
- end
- end
- if GuiButton(gui, buy_wands_open and 10 or 0, 0, "Buy Wands", get_next_id()) then
- hide_research_wands_gui();
- hide_research_spells_gui();
- hide_buy_spells_gui();
- if buy_wands_open then
- hide_buy_wands_gui();
- else
- show_buy_wands_gui();
- end
- end
- if GuiButton(gui, buy_spells_open and 10 or 0, 0, "Buy Spells", get_next_id()) then
- hide_research_wands_gui();
- hide_research_spells_gui();
- hide_buy_wands_gui();
- if buy_spells_open then
- hide_buy_spells_gui();
- else
- show_buy_spells_gui();
- end
- end
- GuiLayoutEnd(gui);
- end };
-end
-
-function hide_menu_gui()
- hide_research_wands_gui();
- hide_research_spells_gui();
- hide_buy_wands_gui();
- hide_buy_spells_gui();
- active_windows["menu"] = nil;
-end
-
-function show_lobby_gui()
- show_menu_gui();
- show_money_gui();
-end
-
-function hide_lobby_gui()
- hide_menu_gui();
- hide_money_gui();
-end
-
-function hide_all_gui()
- active_windows = {};
-end
-
-function gui_update()
- if gui ~= nil then
- if active_windows ~= nil then
- local is_dark_background = false;
- GuiStartFrame(gui);
- for _, window in pairs(active_windows) do
- if window[1] then
- is_dark_background = true;
- end
- end
- if is_dark_background then
- local cx, cy = GameGetCameraPos();
- GameCreateSpriteForXFrames("mods/persistence/files/gui_darken.png", cx, cy);
- end
- local start_gui_id = 14796823;
- for name, window in pairs(active_windows) do
- local gui_id = start_gui_id + simple_string_hash(name);
- window[2](function()
- gui_id = gui_id + 1;
- return gui_id;
- end);
- end
- end
- end
-end
\ No newline at end of file
diff --git a/files/gui/fourslot.lua b/files/gui/fourslot.lua
new file mode 100644
index 0000000..da7fbfb
--- /dev/null
+++ b/files/gui/fourslot.lua
@@ -0,0 +1,302 @@
+if fourslot_loaded~=true then
+ profile_open=false;
+ wands_open=false;
+
+ profile_fourslot = {
+ id = "profile_select",
+ centertext = "SELECT A PROFILE",
+ greentext = "See Auto-Load in Mod Options",
+ redtext = "THERE IS NO UNDO",
+ slot_title = "Profile slot %i:",
+ render_below_footer = function (x_base, y_base, width, height, fourslot_table, _nid)
+ GuiBeginAutoBox(gui);
+
+ GuiZSetForNextWidget(gui, __layer(5));
+ GuiColorNextWidgetBool(gui, not encoder_read_only);
+ GuiOptionsAddForNextWidget(gui, GUI_OPTION.Align_HorizontalCenter);
+ if GuiButton(gui, _nid(), x_base + (width/2), y_base + height + 30, "Play with Persistence") then
+ encoder_read_only = false;
+ end
+ GuiGuideTip(gui, "(Normal) Persistence is enabled, profile saving is automatic.", "");
+
+ GuiZSetForNextWidget(gui, __layer(5));
+ GuiColorNextWidgetBool(gui, encoder_read_only);
+ GuiOptionsAddForNextWidget(gui, GUI_OPTION.Align_HorizontalCenter);
+ if GuiButton(gui, _nid(), x_base + (width/2), y_base + height + 40, "Play in Amnesiac Mode") then
+ encoder_read_only = true;
+ end
+ GuiGuideTip(gui, "Amnesiac Mode will allow players to LOAD a profile (or not)", "but CHANGES WILL NOT BE SAVED.");
+
+ GuiZSetForNextWidget(gui, __layer(5));
+ GuiOptionsAddForNextWidget(gui, GUI_OPTION.Align_HorizontalCenter);
+ if GuiButton(gui, _nid(), x_base + (width/2), y_base + height + 60, "Start without Persistence") then
+ selected_profile_id = DISABLE_PROFILE_ID;
+ end
+ GuiGuideTip(gui, "(Disable) Persistence is disabled. Profiles will not be saved or loaded.", "Click to play.");
+
+ GuiZSetForNextWidget(gui, __layer(4));
+ GuiEndAutoBoxNinePiece(gui, 10);
+ end,
+ render_header_func = function (x_base, y_base, margin, panel_width, panel_height, layer, slot_data, _nid)
+ local row1_y_offset = 4;
+ local row2_y_offset = 14;
+ local x_offset = 16;
+
+ GuiZSetForNextWidget(gui, __layer(layer));
+ GuiColorNextWidgetEnum(gui, COLORS.Green);
+ if slot_data.quickloaded~=nil and slot_data.quickloaded==true then
+ if GuiButton(gui, _nid(), x_base + x_offset, y_base + row1_y_offset, "- Load Profile") then
+ selected_profile_id = slot_data.id;
+ end
+ if fourslot_confirmation == slot_data.id then
+ GuiZSetForNextWidget(gui, __layer(layer));
+ GuiColorNextWidgetEnum(gui, COLORS.Yellow);
+ if GuiButton(gui, _nid(), x_base + x_offset, y_base + row2_y_offset, "- Press again to delete") then
+ fourslot_confirmation = 0;
+ delete_profile(slot_data.id);
+ slot_data.quickloaded = nil;
+ end
+ else
+ GuiZSetForNextWidget(gui, __layer(layer));
+ GuiColorNextWidgetEnum(gui, COLORS.Yellow);
+ if GuiButton(gui, _nid(), x_base + x_offset, y_base + row2_y_offset, "- Delete profile") then
+ fourslot_confirmation = slot_data.id;
+ end
+ end
+ else
+ GuiZSetForNextWidget(gui, __layer(layer));
+ if GuiButton(gui, _nid(), x_base + x_offset, y_base + row1_y_offset, "- Create new profile") then
+ create_new_profile(slot_data.id)
+ end
+ end
+ end,
+ datum_translation = {
+ _index = {[0] = 4, [1] = "money", [2] = "spells_known", [3] = "wand_types_known", [4] = "always_cast_spells_known" },
+ money = {"Stash:", __val, 9, __render_money_stat },
+ spells_known = {"Spells:", __val, 9, __render_gen_stat },
+ wand_types_known = {"Wand Types:", __val, 9, __render_gen_stat },
+ always_cast_spells_known = {"Always Casts:", __val, 9, __render_gen_stat },
+ },
+ datum_exists_member = "quickloaded",
+ slots_func = get_quick_profiles,
+ slots_data = {};
+ };
+
+ wands_fourslot = {
+ id = "wand_select",
+ centertext = "WANDS ARE DESTROYED WHEN RESEARCHED",
+ greentext = "Green stats improve your research",
+ redtext = "Red stats don't improve research",
+ slot_title = "Wand slot %i:",
+ render_slot_func = __render_wand_slot,
+ render_header_func = function (x_base, y_base, margin, panel_width, panel_height, layer, slot_data, _nid)
+ local row1_y_offset = 2;
+ local row2_y_offset = 11;
+ local row3_y_offset = 18;
+ local x_offset = 6;
+
+ if slot_data.e_id~=nil then
+ if slot_data.research.is_new then
+ GuiZSetForNextWidget(gui, __layer(layer));
+ GuiColorNextWidgetBool(gui, last_known_money >= slot_data.cost._sum);
+ if GuiButton(gui, _nid(), x_base + x_offset, y_base + row1_y_offset, string.format("- Research for $ %1.0f", slot_data.cost._sum)) and slot_data.cost._sum < last_known_money then
+ research_wand(slot_data.e_id);
+ slot_data = {};
+ GamePrintImportant("Wand Researched");
+ return true;
+ end
+ else
+ if fourslot_confirmation~=slot_data.id and EntityHasTag(slot_data.e_id, "persistence") then
+ GuiZSetForNextWidget(gui, __layer(layer));
+ GuiColorNextWidgetEnum(gui, COLORS.Green);
+ if GuiButton(gui, _nid(), x_base + x_offset, y_base + row1_y_offset, "- Modify wand") then
+ GamePrint("Modify Wand");
+ present_modify_wand(slot_data.e_id or 0, slot_data.id);
+ end
+ GuiGuideTip(gui, "Click to modify Persistence-created Wand", "Right-click to Recycle (Requires confirmation)");
+ if select(2, GuiGetPreviousWidgetInfo(gui))==true then
+ fourslot_confirmation = slot_data.id;
+ end
+ else
+ if fourslot_confirmation == slot_data.id then
+ GuiZSetForNextWidget(gui, __layer(layer));
+ GuiColorNextWidgetEnum(gui, COLORS.Yellow);
+ if GuiButton(gui, _nid(), x_base + x_offset, y_base + row1_y_offset, "- Click to recycle") then
+ fourslot_confirmation = 0;
+ delete_wand_entity(slot_data.e_id);
+ slot_data = {};
+ GamePrintImportant("Wand Recycled");
+ return true;
+ end
+ else
+ GuiZSetForNextWidget(gui, __layer(layer));
+ GuiColorNextWidgetEnum(gui, COLORS.Dim);
+ if GuiButton(gui, _nid(), x_base + x_offset, y_base + row1_y_offset, "No improved stats") then
+ fourslot_confirmation = slot_data.id;
+ end
+ GuiGuideTip(gui, "Click to recycle wand. No cost. No gain.", "(Requires confirmation)");
+ end
+ end
+ end
+ if #slot_data.wand["spells"]>0 then
+ if slot_data.research.b_spells then
+ GuiColorNextWidgetEnum(gui, COLORS.Red);
+ GuiZSetForNextWidget(gui, __layer(layer));
+ GuiText(gui, x_base + 0 + x_offset, y_base + row2_y_offset, "WAND CONTAINS", small_text_scale);
+ GuiColorNextWidgetEnum(gui, COLORS.Red);
+ GuiZSetForNextWidget(gui, __layer(layer));
+ GuiText(gui, x_base + 2 + x_offset, y_base + row3_y_offset, "UNRESEARCHED SPELLS", small_text_scale);
+ GuiGuideTip(gui, "Spells on wands are destroyed", "At least one spell is unresearched");
+ elseif (not EntityHasTag(slot_data.e_id, "persistence")) then
+ GuiColorNextWidgetEnum(gui, COLORS.Yellow);
+ GuiZSetForNextWidget(gui, __layer(layer));
+ GuiText(gui, x_base + 0 + x_offset, y_base + row2_y_offset, "Wand contains spells which", small_text_scale);
+ GuiColorNextWidgetEnum(gui, COLORS.Yellow);
+ GuiZSetForNextWidget(gui, __layer(layer));
+ GuiText(gui, x_base + 2 + x_offset, y_base + row3_y_offset, "will be lost on research", small_text_scale);
+ GuiGuideTip(gui, "Spells on wands are destroyed", "These spells are all researched");
+ end
+ end
+ else
+ GuiZSetForNextWidget(gui, __layer(layer));
+ GuiColorNextWidgetEnum(gui, COLORS.Green);
+ if GuiButton(gui, _nid(), x_base + x_offset, y_base + row1_y_offset, "- Create new wand") then
+ GamePrint("Create Wand");
+ present_modify_wand(slot_data.e_id or 0, slot_data.id);
+ ---TODO: CREATE WAND
+ end
+ GuiGuideTip(gui, "Create a new wand from researched stats", "Wand Templates can be saved and loaded")
+ end
+ end,
+ datum_submember = "wand",
+ datum_translation = {
+ _index = {[0] = 11, [1] = "sprite", [2] = "spells", [3] = "shuffle", [4] = "spells_per_cast", [5] = "cast_delay", [6] = "recharge_time", [7] = "mana_max", [8] = "mana_charge_speed", [9] = "capacity", [10] = "spread", [11] = "always_cast_spells"},
+ sprite = {"", __val, 0, __render_wand_sprite, "b_wand_types" },
+ spells = {"", __val, 34, __render_wand_spells, "b_capacity" },
+ shuffle = {"$inventory_shuffle", __yesno, 9, __render_gen_stat, "b_shuffle" },
+ spells_per_cast = {"$inventory_actionspercast", __val, 9, __render_gen_stat, "b_spells_per_cast" },
+ cast_delay = {"$inventory_castdelay", __ctime, 9, __render_gen_stat, "b_cast_delay" },
+ recharge_time = {"$inventory_rechargetime", __ctime, 9, __render_gen_stat, "b_recharge_time" },
+ mana_max = {"$inventory_manamax", __round, 9, __render_gen_stat, "b_mana_max" },
+ mana_charge_speed = {"$inventory_manachargespeed", __round, 9, __render_gen_stat, "b_mana_charge_speed" },
+ capacity = {"$inventory_capacity", __val, 9, __render_gen_stat, "b_capacity" },
+ spread = {"$inventory_spread", __deg, 9, __render_gen_stat, "b_spread" },
+ always_cast_spells = {"$inventory_alwayscasts", __val, 9, __render_wand_spells, "b_always_cast_spells" },
+ },
+ datum_exists_member = "wand",
+ slots_func = get_player_wands,
+ slots_data = {};
+ };
+
+ local function draw_fourslot_ui(fourslot_table)
+ local _reload_data=true;
+ fourslot_confirmation=0;
+
+ active_windows[fourslot_table.id] = function (_nid)
+ local function _gui_nop(x_base, y_base, margin, panel_width, panel_height, layer, slot_data, _nid) return; end
+
+ if _reload_data~=false then fourslot_table.slots_data = fourslot_table.slots_func(); _reload_data=false; end
+
+ if fourslot_table.slots_data == nil then return; end
+
+ local x_base = 30;
+ local y_base = 20;
+ local margin = 4;
+ local width = 448;
+ local height = 200;
+ local x_offset = x_base + margin;
+ local y_offset = y_base + margin;
+ local panel_width = 104;
+ local panel_height = height - (margin * 2);
+ GuiZSet(gui, __layer(0)); ---gui frame
+ GuiImageNinePiece(gui, _nid(), x_base, y_base, width, height);
+
+ for _panel_id = 1, 4 do
+ fourslot_table.slots_data[_panel_id].id = _panel_id;
+ local panel_x_offset = x_offset + ((_panel_id-1) * (panel_width + (margin*2)));
+ GuiZSet(gui, __layer(1)); ---per-panel border
+ GuiImageNinePiece(gui, _nid(), panel_x_offset, y_offset, panel_width, panel_height);
+
+ local panel_sub_width = panel_width - (margin*2);
+ local panel_sub_height = panel_height - (margin*2);
+ panel_x_offset = panel_x_offset;
+ panel_y_offset = y_offset;
+
+ local header_x_pos = panel_x_offset;
+ local header_y_pos = panel_y_offset;
+ GuiOptionsAddForNextWidget(gui, GUI_OPTION.GamepadDefaultWidget);
+ if (fourslot_table.render_header_func or _gui_nop)(header_x_pos, header_y_pos, margin, panel_sub_width, panel_sub_height, 2, fourslot_table.slots_data[_panel_id], _nid) then
+ _reload_data = true;
+ end
+
+ local label_x_pos = panel_x_offset + 23;
+ local label_y_pos = header_y_pos + 27;
+ GuiZSetForNextWidget(gui, __layer(2)); ---slot label
+ GuiText(gui, label_x_pos, label_y_pos, string.format(fourslot_table.slot_title, _panel_id), 1);
+
+ local slot_x_pos = panel_x_offset + margin;
+ local slot_y_pos = label_y_pos + 12;
+ panel_sub_width = panel_width - (margin*2);
+ panel_sub_height = panel_sub_height - (slot_y_pos - y_offset - margin);
+ (fourslot_table.render_slot_func or _gui_nop)(slot_x_pos, slot_y_pos, margin, panel_sub_width, panel_sub_height, 2, fourslot_table.slots_data[_panel_id], _nid)
+ local datum_x_pos = panel_x_offset + margin;
+ local datum_y_pos = slot_y_pos + margin;
+ for _order_i = 1, fourslot_table.datum_translation._index[0] do
+ if fourslot_table.slots_data[_panel_id][fourslot_table.datum_exists_member]~=nil then
+ local _datum_name = fourslot_table.datum_translation._index[_order_i];
+ local _datum_value = fourslot_table.datum_submember~=nil and fourslot_table.slots_data[_panel_id][fourslot_table.datum_submember][_datum_name] or fourslot_table.slots_data[_panel_id][_datum_name];
+ local _trans_label = (fourslot_table.datum_translation[_datum_name][1] or "");
+ local _value_func = (fourslot_table.datum_translation[_datum_name][2] or __val);
+ local _height = (fourslot_table.datum_translation[_datum_name][3] or 0);
+ local _render_func = (fourslot_table.datum_translation[_datum_name][4] or _gui_nop);
+ local _datum_table = fourslot_table.slots_data[_panel_id];
+ if fourslot_table.slots_data[_panel_id].research~=nil then
+ local _improves_member = fourslot_table.datum_translation[_datum_name][5];
+ local _improves_bool = fourslot_table.slots_data[_panel_id].research==nil and nil or fourslot_table.slots_data[_panel_id].research[_improves_member];
+ _datum_table.color_val = _improves_bool;
+ end
+
+ _datum_table.label = _trans_label;
+ _datum_table.value = _value_func(_datum_value);
+ _render_func(datum_x_pos, datum_y_pos, margin, panel_sub_width, panel_sub_height, 4, _datum_table, _nid);
+ datum_y_pos = datum_y_pos + _height;
+ end
+ end
+ end
+ __render_tricolor_footer(x_base, y_base, width, height, fourslot_table);
+ if fourslot_table.render_below_footer~=nil then fourslot_table.render_below_footer(x_base, y_base, width, height, fourslot_table, _nid); end
+ end
+ end
+
+ function present_profile_select()
+ if profile_open==true then return; end
+
+ draw_fourslot_ui(profile_fourslot);
+ profile_open = true;
+ end
+
+ function close_profile_select()
+ if profile_open==false then return; end
+
+ active_windows[profile_fourslot.id] = nil;
+ profile_open = false;
+ end
+
+ function present_wands()
+ if wands_open==true then return; end
+
+ draw_fourslot_ui(wands_fourslot);
+ wands_open = true;
+ end
+
+ function close_wands()
+ if wands_open==false then return; end
+
+ active_windows[wands_fourslot.id] = nil;
+ wands_open = false;
+ end
+
+ print("=========================");
+ print("persistence: fourslot loaded.");
+ fourslot_loaded = true;
+end
\ No newline at end of file
diff --git a/files/gui/modify_wand.lua b/files/gui/modify_wand.lua
new file mode 100644
index 0000000..4e9c5c6
--- /dev/null
+++ b/files/gui/modify_wand.lua
@@ -0,0 +1,419 @@
+if modify_wand_loaded~=true then
+ modify_wand_open=false;
+
+ modify_wand_table = {
+ id = "modify_wand",
+ centertext = "CREATE YOUR WAND",
+ greentext = "Stats are limited to researched values.",
+ redtext = "Better wands cost more. No refunds.",
+ slot_title = "Wand slot %i:",
+ render_slot_func = __render_wand_slot,
+ render_header_func = function (x_base, y_base, margin, panel_width, panel_height, layer, slot_data, _nid)
+ local row1_y_offset = 2;
+ local row2_y_offset = 11;
+ local row3_y_offset = 18;
+ local x_offset = 6;
+ slot_data.price = get_wand_buy_price(slot_data.wand);
+
+ local _tmp_price = slot_data.price;
+ if slot_data.origin_e_id~=nil and slot_data.origin_e_id~=0 then
+ local _var_comp = EntityGetFirstComponentIncludingDisabled(slot_data.origin_e_id, "VariableStorageComponent", "persistence_wand_price") or 0;
+ local _origin_price = ComponentGetValue(_var_comp, "value_int");
+ _tmp_price = _tmp_price - _origin_price;
+ GuiZSetForNextWidget(gui, __layer(layer));
+ GuiColorNextWidgetEnum(gui, COLORS.Dim);
+ GuiText(gui, x_base + x_offset, y_base + row2_y_offset, string.format("Paid: $ %i", _origin_price, small_text_scale));
+ end
+
+ GuiZSetForNextWidget(gui, __layer(layer));
+ GuiColorNextWidgetBool(gui, last_known_money >= _tmp_price);
+ if GuiButton(gui, _nid(), x_base + x_offset, y_base + row1_y_offset, string.format("Purchase: $ %1.0f", _tmp_price)) and _tmp_price <= get_player_money() then
+ if slot_data.origin_e_id~=nil and slot_data.origin_e_id~=0 then
+ modify_wand_entity(slot_data);
+ GamePrintImportant("Wand Modified");
+ else
+ slot_data.wand.price = _tmp_price;
+ purchase_wand(slot_data.wand);
+ GamePrintImportant("Wand Purchased");
+ end
+ close_open_windows();
+ return true;
+ end
+ end,
+ datum_translation = {
+ -- = {
+
+
+
diff --git a/persistence.vdf b/persistence.vdf
new file mode 100644
index 0000000..68ac8d9
--- /dev/null
+++ b/persistence.vdf
@@ -0,0 +1,76 @@
+"workshopitem"
+{
+ "appid" "881100"
+ "publishedfileid" "3253132683"
+ "contentfolder" "/home/danin/git/persistence"
+ "previewfile" "/home/danin/git/persistence/workshop_preview_image.png"
+ "title" "Persistence"
+ "description" "Add some Lite to your Rogue
+
+Credit to the original author; https://steamcommunity.com/sharedfiles/filedetails/?id=2144130266
+The mod has undergone a near full rewrite, but without their work this wouldn't have existed.
+The original author wishes to thank the great modding community on the official Noita Discord server https://discord.com/invite/SZtrP2r.
+Without them they would not been able to create this mod.
+I consider Discord an ephemeral information source, which is wasteful of the human resources supporting it. I used the wiki, github, and mods (example and otherwise) to acquire my knowledge.
+Informational efforts would be better suited on a wiki or other quasi-permanent platform than a user-required chat which by necessity fades to history and is fully lost.
+
+I'm happy to share any and all of my modding knowledge to anyone who asks.
+
+
+Finally, my github repo can be found at https://github.com/codefaux/persistence
+
+Now, on with the details:
+
+Persistence has configurable options in the Mod Options menu. Browse this menu to tune difficulty to your tastes, but defaults are a good starting point.
+
+There are five player profile slots. (Slot 5 is only accessible via Mod Options menu)
+
+'Persistence areas' have been added. Persistence areas count as the player's spawn location and Holy Mountains. The Persistence features described below will only work inside a Persistence Area.
+
+'Amnesiac Mode' has been added. This makes Persistence forget all changes upon the death of your character, to give a less Rogue-Lite experience, or to allow expensive testing/experimentation.
+
+Money:
+- An ingame money stash has been added. Each profile stores a separate money stash.
+- Mod Options can lock the stash, only allow deposits, or allow deposits and withdrawals.
+- Mod Options can assign a money payout from stash upon New Game, and upon reaching a Holy Mountain.
+- Mod Options configure how much money is kept on death. Default is 25%.
+- Persistence tracks your last known gold; polymorphed deaths still bank money properly.
+- Transfers into stash or player exceeding a specific balance will be skipped. This is intentional, to avoid overflows.
+
+Spells:
+- Spells can be researched using gold, to be permanently added to your profile's spell list.
+- Researched spells can be purchased for gold. Partially used spells with limited uses cannot be researched.
+- Spells can be recycled if not needed anymore. Recycled spells are simply deleted, there is no cost or benefit.
+- Mod Options can be used to scale spell research and purchase costs.
+- Spell 'Loadouts' can be stored from existing wands in your inventory, or cleared. Loadouts are shared across all profiles.
+- Loadouts must be named. Names must be 1-32 characters. Names do not need to be unique.
+- Stored Loadouts can be purchased. Purchased loadouts will fill your inventory.
+- KNOWN ISSUE: Due to an in-game bug, it requires -extensive- checking to avoid spells overlapping occasionally in inventory. Occasionally, spells will overlap until moved. Nothing will be lost.
+- KNOWN ISSUE: Due to limitations in string length, and how I must store spells, Spell Loadouts have a limited size which is difficult to predict.
+
+Wands:
+- Wands can be researched, storing the best stats you've researched in your profile.
+- Spells on a wand are destroyed when the wand is researched. Unresearched spells will not be automatically researched.
+- Wands can be created using researched stats from your profile.
+- Wands can be recycled if not needed. Recycled wands are simply deleted, there is no cost or benefit.
+- Unique wands cannot be fully researched. Their Type (special shape, name) will not be usable. Their stats will. This is intentional.
+- Wand Templates can be stored or updated during wand creation, or cleared.
+- Wand Templates can be purchased. Purchased wands drop at your location.
+- Wands created using Persistence can be modified.
+- KNOWN ISSUE: Sometimes when researching/recycling/purchasing a wand, the Inventory screen will show spells on the wrong wand, or on nothing at all. Close and re-open the Inventory window.
+
+
+A scanner runs while you explore the world. The scanner is a quality of life feature for Persistence, which can be disabled in Mod Options.
+- If you're standing on or immediately beside a spell or wand in the world, Persistence will indicate if it is unresearched.
+- Wands will indicate wether researched or not.
+- Spells will indicate wether researched or not.
+- Spells ON wands will only show if NOT researched.
+- Wands which only provide a new Type (graphic) will indicate separately.
+
+
+
+Many thanks to everyone using the mod, and doubly so to anyone who reports an issue, suggests a feature, or just says thanks.
+I'd like to thank people directly, but I don't want to violate anyone's privacy.
+If you've made a suggestion I've implemented or reported an issue and would like credit, contact me and tell me how to address you."
+ "changenote" "- Fix for crashes related to attempting to preview empty wand template (proper)"
+}
diff --git a/settings.lua b/settings.lua
new file mode 100644
index 0000000..30bb964
--- /dev/null
+++ b/settings.lua
@@ -0,0 +1,359 @@
+dofile_once("data/scripts/lib/mod_settings.lua");
+-- dofile_once("data/scripts/debug/keycodes.lua");
+
+-- This file can't access other files from this or other mods in all circumstances.
+-- Settings will be automatically saved.
+-- Settings don't have access unsafe lua APIs.
+
+-- Use ModSettingGet() in the game to query settings.
+-- For some settings (for example those that affect world generation) you might want to retain the current value until a certain point, even
+-- if the player has changed the setting while playing.
+-- To make it easy to define settings like that, each setting has a "scope" (e.g. MOD_SETTING_SCOPE_NEW_GAME) that will define when the changes
+-- will actually become visible via ModSettingGet(). In the case of MOD_SETTING_SCOPE_NEW_GAME the value at the start of the run will be visible
+-- until the player starts a new game.
+-- ModSettingSetNextValue() will set the buffered value, that will later become visible via ModSettingGet(), unless the setting scope is MOD_SETTING_SCOPE_RUNTIME.
+
+function mod_setting_integer(mod_id, gui, in_main_menu, im_id, setting)
+ local value = ModSettingGetNextValue(mod_setting_get_id(mod_id,setting));
+ if type(value) ~= "number" then value = setting.value_default or 0; end
+
+ local value_new = GuiSlider(gui, im_id, mod_setting_group_x_offset, 0, setting.ui_name, value, setting.value_min, setting.value_max, setting.value_default, setting.value_display_multiplier or 1, setting.value_display_formatting or "", 64);
+ value_new = math.floor(value_new);
+
+ if value ~= value_new then
+ ModSettingSetNextValue(mod_setting_get_id(mod_id,setting), value_new, false);
+ mod_setting_handle_change_callback(mod_id, gui, in_main_menu, setting, value, value_new);
+ end
+
+ mod_setting_tooltip(mod_id, gui, in_main_menu, setting);
+end
+
+function mod_setting_number_multiple(mod_id, gui, in_main_menu, im_id, setting)
+ local _round_to = setting.round_to or 1;
+
+ local value = ModSettingGetNextValue(mod_setting_get_id(mod_id,setting)) or setting.value_default;
+
+ local value_new = value;
+ value_new = math.floor( (GuiSlider(gui, im_id, mod_setting_group_x_offset, 0, setting.ui_name, value_new, setting.value_min, setting.value_max, setting.value_default, setting.value_display_multiplier or 1, setting.value_display_formatting or "", 64) / _round_to) + 0.5 ) * _round_to;
+
+ if value ~= value_new then
+ ModSettingSetNextValue(mod_setting_get_id(mod_id,setting), value_new, false);
+ mod_setting_handle_change_callback(mod_id, gui, in_main_menu, setting, value, value_new);
+ end
+
+ mod_setting_tooltip(mod_id, gui, in_main_menu, setting);
+end
+
+mod_id = "persistence" -- This should match the name of your mod's folder.
+
+mod_settings_version = 2; -- This is a magic global that can be used to migrate settings to new mod versions. call mod_settings_get_version() before mod_settings_update() to get the old value.
+mod_settings =
+{
+ {
+ category_id = "encoded_settings",
+ ui_name = "",
+ ui_description = "",
+ settings = {
+ { hidden = true, id = "loadout_1", ui_name = "1", ui_description = " ", text_max_length = 512, value_default = "",
+ allowed_characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0123456789, ", scope = MOD_SETTING_SCOPE_RUNTIME, },
+ { hidden = true, id = "loadout_2", ui_name = "2", ui_description = " ", text_max_length = 512, value_default = "",
+ allowed_characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0123456789, ", scope = MOD_SETTING_SCOPE_RUNTIME, },
+ { hidden = true, id = "loadout_3", ui_name = "3", ui_description = " ", text_max_length = 512, value_default = "",
+ allowed_characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0123456789, ", scope = MOD_SETTING_SCOPE_RUNTIME, },
+ { hidden = true, id = "loadout_4", ui_name = "4", ui_description = " ", text_max_length = 512, value_default = "",
+ allowed_characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0123456789, ", scope = MOD_SETTING_SCOPE_RUNTIME, },
+ { hidden = true, id = "loadout_5", ui_name = "5", ui_description = " ", text_max_length = 512, value_default = "",
+ allowed_characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0123456789, ", scope = MOD_SETTING_SCOPE_RUNTIME, },
+ { hidden = true, id = "loadout_6", ui_name = "6", ui_description = " ", text_max_length = 512, value_default = "",
+ allowed_characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0123456789, ", scope = MOD_SETTING_SCOPE_RUNTIME, },
+ { hidden = true, id = "loadout_7", ui_name = "7", ui_description = " ", text_max_length = 512, value_default = "",
+ allowed_characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0123456789, ", scope = MOD_SETTING_SCOPE_RUNTIME, },
+ { hidden = true, id = "loadout_8", ui_name = "8", ui_description = " ", text_max_length = 512, value_default = "",
+ allowed_characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0123456789, ", scope = MOD_SETTING_SCOPE_RUNTIME, },
+ { hidden = true, id = "loadout_9", ui_name = "9", ui_description = " ", text_max_length = 512, value_default = "",
+ allowed_characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0123456789, ", scope = MOD_SETTING_SCOPE_RUNTIME, },
+ { hidden = true, id = "loadout_10", ui_name = "10", ui_description = " ", text_max_length = 512, value_default = "",
+ allowed_characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0123456789, ", scope = MOD_SETTING_SCOPE_RUNTIME, },
+ { hidden = true, id = "loadout_11", ui_name = "11", ui_description = " ", text_max_length = 512, value_default = "",
+ allowed_characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0123456789, ", scope = MOD_SETTING_SCOPE_RUNTIME, },
+ { hidden = true, id = "loadout_12", ui_name = "12", ui_description = " ", text_max_length = 512, value_default = "",
+ allowed_characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0123456789, ", scope = MOD_SETTING_SCOPE_RUNTIME, },
+ { hidden = true, id = "loadout_13", ui_name = "13", ui_description = " ", text_max_length = 512, value_default = "",
+ allowed_characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0123456789, ", scope = MOD_SETTING_SCOPE_RUNTIME, },
+ { hidden = true, id = "loadout_14", ui_name = "14", ui_description = " ", text_max_length = 512, value_default = "",
+ allowed_characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0123456789, ", scope = MOD_SETTING_SCOPE_RUNTIME, },
+ { hidden = true, id = "loadout_15", ui_name = "15", ui_description = " ", text_max_length = 512, value_default = "",
+ allowed_characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0123456789, ", scope = MOD_SETTING_SCOPE_RUNTIME, },
+ { hidden = true, id = "loadout_16", ui_name = "16", ui_description = " ", text_max_length = 512, value_default = "",
+ allowed_characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0123456789, ", scope = MOD_SETTING_SCOPE_RUNTIME, },
+ { hidden = true, id = "loadout_17", ui_name = "17", ui_description = " ", text_max_length = 512, value_default = "",
+ allowed_characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0123456789, ", scope = MOD_SETTING_SCOPE_RUNTIME, },
+ { hidden = true, id = "loadout_18", ui_name = "18", ui_description = " ", text_max_length = 512, value_default = "",
+ allowed_characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0123456789, ", scope = MOD_SETTING_SCOPE_RUNTIME, },
+ { hidden = true, id = "loadout_19", ui_name = "19", ui_description = " ", text_max_length = 512, value_default = "",
+ allowed_characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0123456789, ", scope = MOD_SETTING_SCOPE_RUNTIME, },
+ { hidden = true, id = "loadout_20", ui_name = "20", ui_description = " ", text_max_length = 512, value_default = "",
+ allowed_characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0123456789, ", scope = MOD_SETTING_SCOPE_RUNTIME, },
+ },
+ },
+ {
+ id = "restart_warning",
+ ui_name = "-- NOTE: These settings will not apply until you start a New Game. --",
+ not_setting = true,
+ },
+ {
+ category_id = "liteness_settings",
+ ui_name = "LITENESS",
+ ui_description = "'Lite-ness' settings (Keep money, etc)",
+ settings = {
+ {
+ id = "money_saved_on_death",
+ ui_name = "Money Saved on Death",
+ ui_description = "How much money persists after you do not",
+ ui_fn = mod_setting_number,
+ value_default = 25,
+ value_min = 0,
+ value_max = 100,
+ value_display_formatting = " $0 %",
+ scope = MOD_SETTING_SCOPE_NEW_GAME,
+ },
+ {
+ id = "cap_money_saved_on_death",
+ ui_name = "MAX Money Saved on Death",
+ ui_description = "If you think you're earning too quickly (0 is no limit)",
+ ui_fn = mod_setting_number,
+ value_default = 0,
+ value_min = 0,
+ value_max = 500,
+ value_display_formatting = " $ $0 k",
+ scope = MOD_SETTING_SCOPE_NEW_GAME,
+ },
+ },
+ },
+ {
+ category_id = "default_settings",
+ ui_name = "DEFAULTS",
+ ui_description = "Run defaults",
+ settings = {
+ {
+ id = "gamepad_menu_trigger",
+ ui_name = "Gamepad Menu Trigger",
+ ui_description = "Which key the mod listens to in order to trigger the Persistence menu",
+ value_default = "25",
+ values = { {"23","A"}, {"24", "B"}, {"25", "X"}, {"26", "Y"}, {"11", "DPAD_UP"}, {"12", "DPAD_DOWN"}, {"13", "DPAD_LEFT"}, {"14", "DPAD_RIGHT"} },
+ -- value_default = JOY_BUTTON_X,
+ -- values = { {JOY_BUTTON_A,"A"}, {JOY_BUTTON_B , "B"}, {JOY_BUTTON_X , "X"}, {JOY_BUTTON_Y , "Y"}, {JOY_BUTTON_DPAD_UP , "DPAD_UP"}, {JOY_BUTTON_DPAD_DOWN , "DPAD_DOWN"}, {JOY_BUTTON_DPAD_LEFT , "DPAD_LEFT"}, {JOY_BUTTON_DPAD_RIGHT , "DPAD_RIGHT"} },
+ scope = MOD_SETTING_SCOPE_RUNTIME,
+ },
+ {
+ id = "always_choose_save_id",
+ ui_name = "Load-Save behavior",
+ ui_description = "Manually or automatically load a save (or not)",
+ value_default = "-1",
+ values = { {"-1","Manual"}, {"0","Disable mod"}, {"1","Use Slot 1"}, {"2","Use Slot 2"}, {"3","Use Slot 3"}, {"4","Use Slot 4"}, {"5","Use Slot 5"} },
+ scope = MOD_SETTING_SCOPE_RUNTIME_RESTART,
+ },
+ {
+ id = "allow_stash",
+ ui_name = "(Manual) Stash use",
+ ui_description = "Optionally disallow withdrawals, or manual use altogether. Automatic payouts still work.",
+ value_default = "1",
+ values = { {"1","Allow"}, {"0","Disable Entirely"}, {"-1","Deposit Only"} },
+ scope = MOD_SETTING_SCOPE_NEW_GAME,
+ },
+ {
+ id = "start_with_money",
+ ui_name = "Start with money",
+ ui_description = "Money to withdraw from your Stash at run start",
+ ui_fn = mod_setting_number_multiple,
+ round_to = 25,
+ value_default = 0,
+ value_min = 0,
+ value_max = 5000,
+ value_display_multiplier = 1,
+ value_display_formatting = " $ $0",
+ scope = MOD_SETTING_SCOPE_NEW_GAME,
+ },
+ {
+ id = "holy_mountain_money",
+ ui_name = "Holy Mountain Reward (Stash)",
+ ui_description = "Money to withdraw from your Stash at each Holy Mountain, useful if Stash access is disabled as a challenge",
+ ui_fn = mod_setting_number_multiple,
+ round_to = 25,
+ value_default = 0,
+ value_min = 0,
+ value_max = 5000,
+ value_display_multiplier = 1,
+ value_display_formatting = " $ $0",
+ scope = MOD_SETTING_SCOPE_NEW_GAME,
+ },
+ {
+ id = "holy_mountain_reward",
+ ui_name = "Holy Mountain Reward (Bonus)",
+ ui_description = "Money to reward player for reaching each Holy Mountain",
+ ui_fn = mod_setting_number_multiple,
+ round_to = 50,
+ value_default = 0,
+ value_min = 0,
+ value_max = 10000,
+ value_display_multiplier = 1,
+ value_display_formatting = " $ $0",
+ scope = MOD_SETTING_SCOPE_NEW_GAME,
+ }
+ },
+ },
+ {
+ category_id = "multiplier_settings",
+ ui_name = "MULTIPLIERS",
+ ui_description = "Cost multipliers",
+ settings = {
+ {
+ id = "research_wand_price_multiplier",
+ ui_name = "Wand Research Price Multiplier",
+ ui_description = "Price Multiplier to Research a Wand",
+ ui_fn = mod_setting_number_multiple,
+ round_to = 0.01,
+ value_default = 1,
+ value_min = .1,
+ value_max = 3,
+ value_display_multiplier = 100,
+ value_display_formatting = " $0 %",
+ scope = MOD_SETTING_SCOPE_NEW_GAME,
+ },
+ {
+ id = "research_spell_price_multiplier",
+ ui_name = "Spell Research Price Multiplier",
+ ui_description = "Price Multiplier to Research a Spell",
+ ui_fn = mod_setting_number_multiple,
+ round_to = 0.1,
+ value_default = 10,
+ value_min = 1,
+ value_max = 30,
+ value_display_multiplier = 10,
+ value_display_formatting = " $0 %",
+ scope = MOD_SETTING_SCOPE_NEW_GAME,
+ },
+ {
+ id = "buy_wand_price_multiplier",
+ ui_name = "Buy Wand Price Multiplier",
+ ui_description = "Price Multiplier to Buy a Wand",
+ ui_fn = mod_setting_number_multiple,
+ round_to = 0.01,
+ value_default = 1,
+ value_min = .1,
+ value_max = 3,
+ value_display_multiplier = 100,
+ value_display_formatting = " $0 %",
+ scope = MOD_SETTING_SCOPE_NEW_GAME,
+ },
+ {
+ id = "buy_spell_price_multiplier",
+ ui_name = "Buy Spell Price Multiplier",
+ ui_description = "Price Multiplier to Buy a Spell",
+ ui_fn = mod_setting_number_multiple,
+ round_to = 0.01,
+ value_default = 1,
+ value_min = .1,
+ value_max = 3,
+ value_display_multiplier = 100,
+ value_display_formatting = " $0 %",
+ scope = MOD_SETTING_SCOPE_NEW_GAME,
+ },
+ },
+ },
+ {
+ category_id = "toggle_settings",
+ ui_name = "TOGGLES",
+ ui_description = "Feature toggles",
+ settings = {
+ {
+ id = "show_guide_tips",
+ ui_name = "Show Guide Tooltips in Persistence menus",
+ ui_description = "Hotkeys, feature explanations, etc...",
+ value_default = true,
+ scope = MOD_SETTING_SCOPE_RUNTIME,
+ },
+ {
+ id = "allow_scanner",
+ ui_name = "Entity Scanner",
+ ui_description = "The Entity Scanner watches for nearby spells and wands, and indicates wether or not they have been researched.",
+ value_default = true,
+ scope = MOD_SETTING_SCOPE_RUNTIME,
+ },
+ {
+ hidden = true, -- Unused, keep for future need?
+ id = "move_lobby_to_spawn",
+ ui_name = "Move Lobby to Spawn location",
+ ui_description = "Lobby is normally Starting Cave. Move Lobby to Spawn instead. Meant for Random runs.",
+ value_default = false,
+ scope = MOD_SETTING_SCOPE_NEW_GAME,
+ },
+ {
+ id = "enable_edit_wands_in_lobby",
+ ui_name = "Allow editing Wands in Lobby",
+ ui_description = "Only allowed in Holy Mountain / with perk normally",
+ value_default = false,
+ scope = MOD_SETTING_SCOPE_NEW_GAME,
+ },
+ {
+ id = "enable_teleport_back_up",
+ ui_name = "Allow Teleport to Lobby from within Holy Mountain",
+ ui_description = "Note: There is no return teleport!",
+ value_default = true,
+ scope = MOD_SETTING_SCOPE_NEW_GAME,
+ },
+ {
+ id = "enable_menu_in_holy_mountain",
+ ui_name = "Allow Persistence menu in Holy Mountain",
+ ui_description = "Allow access to Persistence menu (for stash deposit/withdraw, research, buy) in Holy Mountain",
+ value_default = true,
+ scope = MOD_SETTING_SCOPE_NEW_GAME,
+ },
+ {
+ id = "reusable_holy_mountain",
+ ui_name = "Allow Holy Mountain to be reused",
+ ui_description = "Definitely a cheat.",
+ value_default = false,
+ scope = MOD_SETTING_SCOPE_NEW_GAME,
+ },
+ {
+ id = "global_persistence",
+ ui_name = "Always allow Persistence menu access",
+ ui_description = "Because why not?",
+ value_default = false,
+ scope = MOD_SETTING_SCOPE_NEW_GAME,
+ },
+ },
+ },
+}
+
+-- This function is called to ensure the correct setting values are visible to the game via ModSettingGet(). your mod's settings don't work if you don't have a function like this defined in settings.lua.
+-- This function is called:
+-- - when entering the mod settings menu (init_scope will be MOD_SETTINGS_SCOPE_ONLY_SET_DEFAULT)
+-- - before mod initialization when starting a new game (init_scope will be MOD_SETTING_SCOPE_NEW_GAME)
+-- - when entering the game after a restart (init_scope will be MOD_SETTING_SCOPE_RESTART)
+-- - at the end of an update when mod settings have been changed via ModSettingsSetNextValue() and the game is unpaused (init_scope will be MOD_SETTINGS_SCOPE_RUNTIME)
+function ModSettingsUpdate(init_scope)
+ local old_version = mod_settings_get_version(mod_id) -- This can be used to migrate some settings between mod versions.
+ if old_version==1 then
+ -- need to update money_saved_on_death * 100
+ money_saved_on_death = ModSettingGet("persistence.money_saved_on_death");
+ ModSettingSet("persistence.money_saved_on_death", money_saved_on_death * 100, money_saved_on_death==25);
+ end
+ mod_settings_update(mod_id, mod_settings, init_scope)
+end
+
+-- This function should return the number of visible setting UI elements.
+-- Your mod's settings wont be visible in the mod settings menu if this function isn't defined correctly.
+-- If your mod changes the displayed settings dynamically, you might need to implement custom logic.
+-- The value will be used to determine whether or not to display various UI elements that link to mod settings.
+-- At the moment it is fine to simply return 0 or 1 in a custom implementation, but we don't guarantee that will be the case in the future.
+-- This function is called every frame when in the settings menu.
+function ModSettingsGuiCount()
+ return mod_settings_gui_count(mod_id, mod_settings)
+end
+
+-- This function is called to display the settings UI for this mod. Your mod's settings wont be visible in the mod settings menu if this function isn't defined correctly.
+function ModSettingsGui(gui, in_main_menu)
+ mod_settings_gui(mod_id, mod_settings, gui, in_main_menu)
+end
diff --git a/workshop.xml b/workshop.xml
index 5ff49de..7d3a1a0 100644
--- a/workshop.xml
+++ b/workshop.xml
@@ -1,7 +1,74 @@
+ dont_upload_folders=".vscode|.git|.github"
+ dont_upload_files=".gitignore|README.md|files/debug.lua|persistence.pdn|persistence.vdf|persistence.code-workspace|TODO.md|README.md">
\ No newline at end of file
diff --git a/workshop_id.txt b/workshop_id.txt
index 234ae44..88e5138 100644
--- a/workshop_id.txt
+++ b/workshop_id.txt
@@ -1 +1 @@
-2144130266
\ No newline at end of file
+3253132683
\ No newline at end of file
diff --git a/workshop_preview_image.png b/workshop_preview_image.png
index e4b968d..b80d82a 100644
Binary files a/workshop_preview_image.png and b/workshop_preview_image.png differ