1
0
mirror of https://github.com/Refactorio/RedMew.git synced 2024-12-12 10:04:40 +02:00

Diggy - New leveling system (#402)

* Basic setup for Force Control

* Buffs transitioned to new level system

Implemented level up buffs (Also made a double value avaliable at certain levels)

Added experience for research, rocket launched, mined rocks

Removing experience on player death. 0.5% of total experience

Added total_experience to ForceControl.lua

Refactored util/game.lua

* Transfered market item unlocks

Made Market Items unlock through the force control system.

Removed stone from mined stone

* Initial conversion of market items to coins

* Started biter killing giving XP

* XP for killing enemies. GUI update

* Made ForceControl.remove_experience return the actual removed experience

* Got my head and mind back, also fixed progress bar saying long numbers

* Changed get_market()

* Brand new formula, CI review changes

* Update CHANGELOG.md
This commit is contained in:
Simon 2018-11-24 10:45:43 +01:00 committed by Valansch
parent 1940b56ddf
commit 22c21011ab
8 changed files with 500 additions and 463 deletions

View File

@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
## Next version
### Features
- [Diggy] Added new formula to calculate experience requirement for next level #402
- [Diggy] Added health bonus on level up #402
- [Diggy] Added new level system to replace stone sent to surface #402
- [Diggy] Mining rocks no longer gives stone or coal #402
- [Diggy] Added particles when a biter mines a rock upon spawning #424
- [Diggy] Added bot mining #434
- [Map] Add Tetris map #433

View File

@ -2,6 +2,7 @@
local Global = require 'utils.global'
local Event = require 'utils.event'
local raise_event = script.raise_event
local ceil = math.ceil
local max = math.max
-- this, things that can be done run-time
@ -18,19 +19,15 @@ local ForceControlBuilder = {}
-- all force data being monitored
local forces = {}
-- the table holding the function that calculates the experience to next level
local next_level_cap_calculator = {
execute = nil
}
-- the function that calculates the experience to next level
local calculate_next_level_cap = nil
Global.register(
{
forces = forces,
next_level_cap_calculator = next_level_cap_calculator
},
function(tbl)
forces = tbl.forces
next_level_cap_calculator = tbl.next_level_cap_calculator
end
)
@ -147,11 +144,11 @@ end
---Register the config and initialize the feature.
---@param level_up_formula function
function ForceControl.register(level_up_formula)
if next_level_cap_calculator.execute then
if calculate_next_level_cap then
error('Can only register one force control.')
end
next_level_cap_calculator.execute = level_up_formula
calculate_next_level_cap = level_up_formula
return ForceControlBuilder
end
@ -159,7 +156,7 @@ end
---Registers a new force to participate.
---@param lua_force_or_name LuaForce|string
function ForceControl.register_force(lua_force_or_name)
if not next_level_cap_calculator.execute then
if not calculate_next_level_cap then
error('Can only register a force when the config has been initialized via ForceControl.register(config_table).')
end
local force = get_valid_force(lua_force_or_name)
@ -169,14 +166,15 @@ function ForceControl.register_force(lua_force_or_name)
forces[force.name] = {
current_experience = 0,
total_experience = 0,
current_level = 0,
experience_level_up_cap = next_level_cap_calculator.execute(0)
experience_level_up_cap = calculate_next_level_cap(0)
}
end
---Returns the ForceControlBuilder.
function ForceControl.get_force_control_builder()
if not next_level_cap_calculator.execute then
if not calculate_next_level_cap then
error('Can only get the force control builder when the config has been initialized via ForceControl.register(config_table).')
end
@ -185,7 +183,8 @@ end
---Removes experience from a force. Won't cause de-level nor go below 0.
---@param lua_force_or_name LuaForce|string
---@param experience number amount of experience to add
---@param experience number amount of experience to remove
---@return number the experience being removed
function ForceControl.remove_experience(lua_force_or_name, experience)
assert_type('number', experience, 'Argument experience of function ForceControl.remove_experience')
@ -200,14 +199,39 @@ function ForceControl.remove_experience(lua_force_or_name, experience)
if not force_config then
return
end
local backup_current_experience = force_config.current_experience
force_config.current_experience = max(0, force_config.current_experience - experience)
force_config.total_experience = (force_config.current_experience == 0) and force_config.total_experience - backup_current_experience or max(0, force_config.total_experience - experience)
return backup_current_experience - force_config.current_experience
end
---Removes experience from a force, based on a percentage of the total obtained experience
---@param lua_force_or_name LuaForce|string
---@param percentage number percentage of total obtained experience to remove
---@param min_experience number minimum amount of experience to remove (optional)
---@return number the experience being removed
---@see ForceControl.remove_experience
function ForceControl.remove_experience_percentage(lua_force_or_name, percentage, min_experience)
local min_experience = min_experience ~= nil and min_experience or 0
local force = get_valid_force(lua_force_or_name)
if not force then
return
end
local force_config = forces[force.name]
if not force_config then
return
end
local penalty = force_config.total_experience * percentage
penalty = (penalty >= min_experience) and ceil(penalty) or ceil(min_experience)
return ForceControl.remove_experience(lua_force_or_name, penalty)
end
---Adds experience to a force.
---@param lua_force_or_name LuaForce|string
---@param experience number amount of experience to add
function ForceControl.add_experience(lua_force_or_name, experience)
---@param resursive_call boolean whether or not the function is called recursively (optional)
function ForceControl.add_experience(lua_force_or_name, experience, recursive_call)
assert_type('number', experience, 'Argument experience of function ForceControl.add_experience')
if experience < 1 then
@ -224,6 +248,9 @@ function ForceControl.add_experience(lua_force_or_name, experience)
local new_experience = force_config.current_experience + experience
local experience_level_up_cap = force_config.experience_level_up_cap
if not recursive_call then
force_config.total_experience = force_config.total_experience + experience
end
if (new_experience < experience_level_up_cap) then
force_config.current_experience = new_experience
@ -234,11 +261,10 @@ function ForceControl.add_experience(lua_force_or_name, experience)
local new_level = force_config.current_level + 1
force_config.current_level = new_level
force_config.current_experience = 0
force_config.experience_level_up_cap = next_level_cap_calculator.execute(new_level)
force_config.experience_level_up_cap = calculate_next_level_cap(new_level)
raise_event(ForceControl.events.on_level_up, {level_reached = new_level, force = force})
ForceControl.add_experience(force, new_experience - experience_level_up_cap)
ForceControl.add_experience(force, new_experience - experience_level_up_cap, true)
end
---Return the force data as {
@ -261,10 +287,29 @@ function ForceControl.get_force_data(lua_force_or_name)
return {
current_experience = force_config.current_experience,
total_experience = force_config.total_experience,
current_level = force_config.current_level,
experience_level_up_cap = force_config.experience_level_up_cap,
experience_percentage = (force_config.current_experience / force_config.experience_level_up_cap) * 100
}
end
function ForceControl.get_formatted_force_data(lua_force_or_name)
local force_config = ForceControl.get_force_data(lua_force_or_name)
if not force_config then
return
end
local result =
string.format(
'Current experience: %d Total experience: %d Current level: %d Next level at: %d Percentage to level up: %d%%',
force_config.current_experience,
force_config.total_experience,
force_config.current_level,
force_config.experience_level_up_cap,
math.floor(force_config.experience_percentage * 100) / 100
)
return result
end
return ForceControl

View File

@ -343,7 +343,7 @@ local Config = {
-- market config
market_spawn_position = {x = 0, y = 3},
stone_to_surface_amount = 50,
currency_item = 'stone',
currency_item = 'coin',
-- locations where chests will be automatically cleared from currency_item
void_chest_tiles = {
@ -355,52 +355,42 @@ local Config = {
-- add or remove a table entry to add or remove a unlockable item from the mall.
-- format: {unlock_at_level, price, prototype_name},
-- alternative format: {level = 32, price = {{"stone", 2500}, {"coin", 100}}, name = 'power-armor'},
unlockables = require('map_gen.Diggy.FormatMarketItems').initialize_unlockables(
{
{level = 1, price = 50, name = 'iron-axe'},
{level = 2, price = 50, name = 'raw-wood'},
{level = 2, price = 20, name = 'raw-fish'},
{level = 3, price = 50, name = 'stone-brick'},
{level = 5, price = 125, name = 'stone-wall'},
{level = 7, price = 25, name = 'small-lamp'},
{level = 9, price = 50, name = 'firearm-magazine'},
{level = 9, price = 250, name = 'pistol'},
{level = 11, price = 850, name = 'shotgun'},
{level = 11, price = 50, name = 'shotgun-shell'},
{level = 14, price = 500, name = 'light-armor'},
{level = 16, price = 850, name = 'submachine-gun'},
{level = 16, price = 50, name = 'steel-axe'},
{level = 19, price = 100, name = 'piercing-rounds-magazine'},
{level = 19, price = 100, name = 'piercing-shotgun-shell'},
{level = 21, price = 750, name = 'heavy-armor'},
{level = 25, price = 1500, name = 'modular-armor'},
{level = 25, price = 1000, name = 'landfill'},
{level = 28, price = {{"stone", 900}, {"coin", 25}}, name = 'personal-roboport-equipment'},
{level = 28, price = {{"stone", 250}, {"coin", 10}}, name = 'construction-robot'},
{level = 32, price = {{"stone", 2500}, {"coin", 100}}, name = 'power-armor'},
{level = 34, price = {{"stone", 150}, {"coin", 10}}, name = 'battery-equipment'},
{level = 33, price = {{"stone", 2000}, {"coin", 75}}, name = 'fusion-reactor-equipment'},
{level = 36, price = {{"stone", 750}, {"coin", 20}}, name = 'energy-shield-equipment'},
{level = 1, price = 5, name = 'iron-axe'},
{level = 2, price = 5, name = 'raw-wood'},
{level = 2, price = 10, name = 'raw-fish'},
{level = 3, price = 5, name = 'stone-brick'},
{level = 5, price = 12, name = 'stone-wall'},
{level = 7, price = 6, name = 'small-lamp'},
{level = 9, price = 5, name = 'firearm-magazine'},
{level = 9, price = 25, name = 'pistol'},
{level = 11, price = 85, name = 'shotgun'},
{level = 11, price = 5, name = 'shotgun-shell'},
{level = 14, price = 50, name = 'light-armor'},
{level = 16, price = 85, name = 'submachine-gun'},
{level = 16, price = 25, name = 'steel-axe'},
{level = 19, price = 15, name = 'piercing-rounds-magazine'},
{level = 19, price = 15, name = 'piercing-shotgun-shell'},
{level = 21, price = 100, name = 'heavy-armor'},
{level = 25, price = 250, name = 'modular-armor'},
{level = 25, price = 100, name = 'landfill'},
{level = 28, price = 250, name = 'personal-roboport-equipment'},
{level = 28, price = 75, name = 'construction-robot'},
{level = 32, price = 850, name = 'power-armor'},
{level = 34, price = 100, name = 'battery-equipment'},
{level = 33, price = 1000, name = 'fusion-reactor-equipment'},
{level = 36, price = 150, name = 'energy-shield-equipment'},
{level = 42, price = 1000, name = 'combat-shotgun'},
{level = 46, price = 250, name = 'uranium-rounds-magazine'},
{level = 58, price = {{"stone", 1500}, {"coin", 25}}, name = 'rocket-launcher'},
{level = 58, price = {{"stone", 500}, {"coin", 5}}, name = 'rocket'},
{level = 66, price = {{"stone", 1000}, {"coin", 10}}, name = 'explosive-rocket'},
{level = 73, price = {{"stone", 1000}, {"coin", 200}}, name = 'satellite'},
{level = 100, price = {{"stone", 5000}, {"coin", 9999}}, name = 'atomic-bomb'},
{level = 58, price = 250, name = 'rocket-launcher'},
{level = 58, price = 50, name = 'rocket'},
{level = 66, price = 100, name = 'explosive-rocket'},
{level = 73, price = 2000, name = 'satellite'},
{level = 100, price = 1, name = 'iron-stick'},
}
),
buffs = { --Define new buffs here, they are handed out for each level
{prototype = {name = 'mining_speed', value = 5}},
{prototype = {name = 'inventory_slot', value = 1}},
{prototype = {name = 'stone_automation', value = 3}},
},
-- controls the formula for calculating level up costs in stone sent to surface
difficulty_scale = 25, -- Diggy default 25. Higher increases difficulty, lower decreases (Only affects the stone requirement/cost to level up) (Only integers has been tested succesful)
start_stone = 50, -- Diggy default 50. This sets the price for the first level.
cost_precision = 2, -- Diggy default 2. This sets the precision of the stone requirements to level up. E.g. 1234 becomes 1200 with precision 2 and 1230 with precision 3.
},
@ -409,7 +399,40 @@ local Config = {
enabled = true,
autojail = true,
allowed_collapses_first_hour = 4
}
},
Experience = {
enabled = true,
-- controls the formula for calculating level up costs in stone sent to surface
difficulty_scale = 25, -- Diggy default 25. Higher increases experience requirement climb
first_lvl_xp = 600, -- Diggy default 600. This sets the price for the first level.
xp_fine_tune = 200, -- Diggy default 200. This value is used to fine tune the overall requirement climb without affecting the speed
cost_precision = 3, -- Diggy default 3. This sets the precision of the required experience to level up. E.g. 1234 becomes 1200 with precision 2 and 1230 with precision 3.
-- percentage * mining productivity level gets added to mining speed
mining_speed_productivity_multiplier = 5,
XP = {
['sand-rock-big'] = 5,
['rock-huge'] = 10,
['rocket_launch'] = 5000, -- XP reward for a single rocket launch
['science-pack-1'] = 2,
['science-pack-2'] = 4,
['science-pack-3'] = 10,
['military-science-pack'] = 8,
['production-science-pack'] = 25,
['high-tech-science-pack'] = 50,
['space-science-pack'] = 10,
['enemy_killed'] = 10, -- Base XP for killing biters and spitters. This value is multiplied by the alien_coin_modifiers from ArtefactHunting
['death-penalty'] = 0.005, -- XP deduct in percentage of total experience when a player dies (Diggy default: 0.005 which equals 0.5%)
},
buffs = { --Define new buffs here, they are handed out for each level.
['mining_speed'] = {value = 5},
['inventory_slot'] = {value = 1},
['health_bonus'] = {value = 2.5, double_level = 5}, -- double_level is the level interval for receiving a double bonus (Diggy default: 5 which equals every 5th level)
},
},
},
}

View File

@ -24,42 +24,6 @@ local Simplex = require 'map_gen.shared.simplex_noise'
-- this
local DiggyHole = {}
-- keeps track of the amount of times per player when they mined with a full inventory in a row
local full_inventory_mining_cache = {}
Global.register({
full_inventory_mining_cache = full_inventory_mining_cache,
}, function (tbl)
full_inventory_mining_cache = tbl.full_inventory_mining_cache
end)
local function reset_player_full_inventory_cache(player)
if not full_inventory_mining_cache[player.index] then
return
end
full_inventory_mining_cache[player.index] = nil
end
local full_inventory_message = 'Miner, you have a full inventory!\n\nMake sure to empty it before you continue digging.'
local function trigger_inventory_warning(player)
local player_index = player.index
local count = full_inventory_mining_cache[player_index]
if not count then
full_inventory_mining_cache[player_index] = 1
player.print('## - ' .. full_inventory_message, {r = 1, g = 1, b = 0, a = 1})
player.play_sound{path='utility/new_objective', volume_modifier = 1 }
return
end
full_inventory_mining_cache[player_index] = count + 1
if count % 5 == 0 then
require 'features.gui.popup'.player(player, full_inventory_message)
end
end
--[[--
Triggers a diggy diggy hole for a given sand-rock-big or rock-huge.
@ -287,8 +251,9 @@ function DiggyHole.register(config)
if name == 'sand-rock-big' or name == 'rock-huge' then
event.buffer.remove({name = 'coal', count = 100})
event.buffer.remove({name = 'stone', count = 100})
-- this logic can be replaced once we've fully replaced the stone to surface functionality
--[[ this logic can be replaced once we've fully replaced the stone to surface functionality
if enable_digging_warning then
local player = Game.get_player_by_index(event.player_index)
if player and player.valid then
@ -298,7 +263,7 @@ function DiggyHole.register(config)
trigger_inventory_warning(player)
end
end
end
end ]]
end
diggy_hole(entity)
@ -351,6 +316,7 @@ end
function DiggyHole.on_init()
game.forces.player.technologies['landfill'].enabled = false
game.forces.player.technologies['atomic-bomb'].enabled = false
end
return DiggyHole

View File

@ -0,0 +1,308 @@
-- dependencies
local Event = require 'utils.event'
local Game = require 'utils.game'
local Global = require 'utils.global'
local ForceControl = require 'features.force_control'
local Debug = require 'map_gen.Diggy.Debug'
-- Will be registered in Experience.register
local ForceControl_builder = {}
-- this
local Experience = {}
local mining_efficiency = {
active_modifier = 0,
research_modifier = 0,
level_modifier = 0,
}
local inventory_slots = {
active_modifier = 0,
research_modifier = 0,
level_modifier = 0,
}
local health_bonus = {
active_modifier = 0,
research_modifier = 0,
level_modifier = 0,
}
Global.register({
mining_efficiency = mining_efficiency,
inventory_slots = inventory_slots,
health_bonus = health_bonus
}, function(tbl)
mining_efficiency = tbl.mining_efficiency
inventory_slots = tbl.inventory_slots
health_bonus = tbl.health_bonus
end)
local Config = {}
local string_format = string.format
local alien_coin_modifiers = require 'map_gen.Diggy.Config'.features.ArtefactHunting.alien_coin_modifiers
local level_up_formula = (function (level_reached)
local floor = math.floor
local log = math.log
local Config = require 'map_gen.Diggy.Config'.features.Experience
local difficulty_scale = floor(Config.difficulty_scale)
local level_fine_tune = floor(Config.xp_fine_tune)
local start_value = (floor(Config.first_lvl_xp)/2)
local precision = (floor(Config.cost_precision))
local function formula(level)
return (
difficulty_scale * (level) ^ 3
+ (level_fine_tune + start_value) * (level) ^ 2
+ start_value * (level)
- difficulty_scale * (level)
- level_fine_tune * (level)
)
end
local value = formula(level_reached + 1)
local lower_value = formula(level_reached)
value = value - (value % (10 ^ (floor(log(value,10)) - precision)))
if lower_value == 0 then
return value - lower_value
end
lower_value = lower_value - (lower_value % (10 ^ (floor(log(lower_value,10)) - precision)))
return value - lower_value
end)
---Updates a forces manual mining speed modifier. By removing active modifiers and re-adding
---@param force LuaForce the force of which will be updated
---@param level_up number a level if updating as part of a level up (optional)
function Experience.update_mining_speed(force, level_up)
local level_up = level_up ~= nil and level_up or 0
local buff = Config.buffs['mining_speed']
if level_up > 0 and buff ~= nil then
local value = (buff.double_level ~= nil and level_up%buff.double_level == 0) and buff.value*2 or buff.value
mining_efficiency.level_modifier = mining_efficiency.level_modifier + (value * 0.01)
end
-- remove the current buff
local old_modifier = force.manual_mining_speed_modifier - mining_efficiency.active_modifier
-- update the active modifier
mining_efficiency.active_modifier = mining_efficiency.research_modifier + mining_efficiency.level_modifier
-- add the new active modifier to the non-buffed modifier
force.manual_mining_speed_modifier = old_modifier + mining_efficiency.active_modifier
end
---Updates a forces inventory slots. By removing active modifiers and re-adding
---@param force LuaForce the force of which will be updated
---@param level_up number a level if updating as part of a level up (optional)
function Experience.update_inventory_slots(force, level_up)
local level_up = level_up ~= nil and level_up or 0
local buff = Config.buffs['inventory_slot']
if level_up > 0 and buff ~= nil then
local value = (buff.double_level ~= nil and level_up%buff.double_level == 0) and buff.value*2 or buff.value
inventory_slots.level_modifier = inventory_slots.level_modifier + value
end
-- remove the current buff
local old_modifier = force.character_inventory_slots_bonus - inventory_slots.active_modifier
-- update the active modifier
inventory_slots.active_modifier = inventory_slots.research_modifier + inventory_slots.level_modifier
-- add the new active modifier to the non-buffed modifier
force.character_inventory_slots_bonus = old_modifier + inventory_slots.active_modifier
end
---Updates a forces inventory slots. By removing active modifiers and re-adding
---@param force LuaForce the force of which will be updated
---@param level_up number a level if updating as part of a level up (optional)
function Experience.update_health_bonus(force, level_up)
local level_up = level_up ~= nil and level_up or 0
local buff = Config.buffs['health_bonus']
if level_up > 0 and buff ~= nil then
local value = (buff.double_level ~= nil and level_up%buff.double_level == 0) and buff.value*2 or buff.value
health_bonus.level_modifier = health_bonus.level_modifier + value
end
-- remove the current buff
local old_modifier = force.character_health_bonus - health_bonus.active_modifier
-- update the active modifier
health_bonus.active_modifier = health_bonus.research_modifier + health_bonus.level_modifier
-- add the new active modifier to the non-buffed modifier
force.character_health_bonus = old_modifier + health_bonus.active_modifier
end
-- declaration of variables to prevent table lookups @see Experience.register
local sand_rock_xp
local rock_huge_xp
---Awards experience when a rock has been mined
---@param event LuaEvent
local function on_player_mined_entity(event)
local entity = event.entity
local player_index = event.player_index
local force = Game.get_player_by_index(player_index).force
local exp
if entity.name == 'sand-rock-big' then
exp = sand_rock_xp
elseif entity.name == 'rock-huge' then
exp = rock_huge_xp
else
return
end
local text = string_format('+%d XP', exp)
Game.print_player_floating_text_position(player_index, text, {r = 144, g = 202, b = 249},0, -0.5)
ForceControl.add_experience(force, exp)
Debug.print(ForceControl.get_formatted_force_data(force))
end
---Awards experience when a research has finished, based on ingredient cost of research
---@param event LuaEvent
local function on_research_finished(event)
local research = event.research
local force = research.force
local award_xp = 0
for _, ingredient in pairs(research.research_unit_ingredients) do
local name = ingredient.name
local reward = Config.XP[name]
award_xp = award_xp + reward
end
local exp = award_xp * research.research_unit_count
local text = string_format('Research completed! +%d XP', exp)
for _, p in pairs(game.connected_players) do
local player_index = p.index
Game.print_player_floating_text_position(player_index, text, {r = 144, g = 202, b = 249}, -1, -0.5)
end
ForceControl.add_experience(force, exp)
local current_modifier = mining_efficiency.research_modifier
local new_modifier = force.mining_drill_productivity_bonus * Config.mining_speed_productivity_multiplier * 0.5
if (current_modifier == new_modifier) then
-- something else was researched
return
end
mining_efficiency.research_modifier = new_modifier
inventory_slots.research_modifier = force.mining_drill_productivity_bonus * 50 -- 1 per level
Experience.update_inventory_slots(force, 0)
Experience.update_mining_speed(force, 0)
game.forces.player.technologies['landfill'].enabled = false
end
---Awards experience when a rocket has been launched
---@param event LuaEvent
local function on_rocket_launched(event)
local exp = Config.XP['rocket_launch']
local force = event.force
local text = string_format('Rocket launched! +%d XP', exp)
for _, p in pairs(game.connected_players) do
local player_index = p.index
Game.print_player_floating_text_position(player_index, text, {r = 144, g = 202, b = 249},-1, -0.5)
end
ForceControl.add_experience(force, exp)
end
---Awards experience when a player kills an enemy, based on type of enemy
---@param event LuaEvent
local function on_entity_died (event)
local entity = event.entity
local force = entity.force
if force.name ~= 'enemy' then
return
end
local cause = event.cause
if not cause or cause.type ~= 'player' or not cause.valid then
return
end
local exp = Config.XP['enemy_killed'] * alien_coin_modifiers[entity.name]
local text = string_format('Killed %s! + %d XP', entity.name, exp)
local player_index = cause.player.index
Game.print_player_floating_text_position(player_index, text, {r = 144, g = 202, b = 249},-1, -0.5)
ForceControl.add_experience(force, exp)
end
---Deducts experience when a player respawns, based on a percentage of total experience
---@param event LuaEvent
local function on_player_respawned(event)
local player = Game.get_player_by_index(event.player_index)
local force = player.force
local exp = ForceControl.remove_experience_percentage(force, Config.XP['death-penalty'], 50)
local text = string_format('%s died! -%d XP', player.name, exp)
for _, p in pairs(game.connected_players) do
Game.print_player_floating_text_position(p.index, text, {r = 255, g = 0, b = 0},-1, -0.5)
end
end
---Get list of defined buffs
---@return table with the same format as in the Diggy Config
---@see Diggy.Config.features.Experience.Buffs
function Experience.get_buffs()
return Config.buffs
end
local level_table = {}
---Get experiment requirement for a given level
---Primarily used for the market GUI to display total experience required to unlock a specific item
---@param level number a number specifying the level
---@return number required total experience to reach supplied level
function Experience.calculate_level_xp(level)
if level_table[level] == nil then
local value
if level == 1 then
value = level_up_formula(level-1)
else
value = level_up_formula(level-1)+Experience.calculate_level_xp(level-1)
end
table.insert(level_table, level, value)
end
return level_table[level]
end
function Experience.register(cfg)
Config = cfg
--Adds the function on how to calculate level caps (When to level up)
ForceControl_builder = ForceControl.register(level_up_formula)
--Adds a function that'll be executed at every level up
ForceControl_builder.register_on_every_level(function (level_reached, force)
force.print(string_format('%s Leveled up to %d!', '## - ', level_reached))
force.play_sound{path='utility/new_objective', volume_modifier = 1 }
local Experience = require 'map_gen.Diggy.Feature.Experience'
Experience.update_inventory_slots(force, level_reached)
Experience.update_mining_speed(force, level_reached)
Experience.update_health_bonus(force, level_reached)
local MarketExchange = require 'map_gen.Diggy.Feature.MarketExchange'
local market = MarketExchange.get_market()
MarketExchange.update_market_contents(market, force)
MarketExchange.update_gui()
end)
-- Events
Event.add(defines.events.on_player_mined_entity, on_player_mined_entity)
Event.add(defines.events.on_research_finished, on_research_finished)
Event.add(defines.events.on_rocket_launched, on_rocket_launched)
Event.add(defines.events.on_player_respawned, on_player_respawned)
Event.add(defines.events.on_entity_died, on_entity_died)
-- Prevents table lookup thousands of times
sand_rock_xp = Config.XP['sand-rock-big']
rock_huge_xp = Config.XP['rock-huge']
end
function Experience.on_init()
--Adds the 'player' force to participate in the force control system.
local force = game.forces.player
ForceControl.register_force(force)
end
return Experience

View File

@ -11,10 +11,11 @@ local Debug = require 'map_gen.Diggy.Debug'
local Template = require 'map_gen.Diggy.Template'
local Global = require 'utils.global'
local Game = require 'utils.game'
local MarketUnlockables = require 'map_gen.Diggy.MarketUnlockables'
local calculate_level = MarketUnlockables.calculate_level
local insert = table.insert
local force_control = require 'features.force_control'
local Experience = require 'map_gen.Diggy.Feature.Experience'
local max = math.max
local floor = math.floor
local utils = require 'utils.utils'
local prefix = '## - '
@ -23,114 +24,20 @@ local MarketExchange = {}
local config = {}
local stone_tracker = {
stone_sent_to_surface = 0,
previous_stone_sent_to_surface = 0,
current_level = 0,
}
local stone_collecting = {
initial_value = 0,
active_modifier = 0,
research_modifier = 0,
market_modifier = 0,
}
local mining_efficiency = {
active_modifier = 0,
research_modifier = 0,
market_modifier = 0,
}
local inventory_slots = {
active_modifier = 0,
research_modifier = 0,
market_modifier = 0,
}
Global.register({
stone_collecting = stone_collecting,
stone_tracker = stone_tracker,
mining_efficiency = mining_efficiency,
inventory_slots = inventory_slots,
}, function(tbl)
stone_collecting = tbl.stone_collecting
stone_tracker = tbl.stone_tracker
mining_efficiency = tbl.mining_efficiency
inventory_slots = tbl.inventory_slots
end)
local function send_stone_to_surface(total)
stone_tracker.previous_stone_sent_to_surface = stone_tracker.stone_sent_to_surface
stone_tracker.stone_sent_to_surface = stone_tracker.stone_sent_to_surface + total
end
local on_market_timeout_finished = Token.register(function(params)
Template.market(params.surface, params.position, params.player_force, {})
local tiles = {}
for _, position in pairs(params.void_chest_tiles) do
insert(tiles, {name = 'tutorial-grid', position = position})
end
params.surface.set_tiles(tiles)
end)
local function update_mining_speed(force)
-- remove the current buff
local old_modifier = force.manual_mining_speed_modifier - mining_efficiency.active_modifier
-- update the active modifier
mining_efficiency.active_modifier = mining_efficiency.research_modifier + mining_efficiency.market_modifier
-- add the new active modifier to the non-buffed modifier
force.manual_mining_speed_modifier = old_modifier + mining_efficiency.active_modifier
end
local function update_inventory_slots(force)
-- remove the current buff
local old_modifier = force.character_inventory_slots_bonus - inventory_slots.active_modifier
-- update the active modifier
inventory_slots.active_modifier = inventory_slots.research_modifier + inventory_slots.market_modifier
-- add the new active modifier to the non-buffed modifier
force.character_inventory_slots_bonus = old_modifier + inventory_slots.active_modifier
end
local function update_stone_collecting()
-- remove the current buff
local old_modifier = stone_collecting.initial_value - stone_collecting.active_modifier
-- update the active modifier
stone_collecting.active_modifier = stone_collecting.research_modifier + stone_collecting.market_modifier
-- add the new active modifier to the non-buffed modifier
stone_collecting.initial_value = old_modifier + stone_collecting.active_modifier
end
--Handles the updating of market items when unlocked, also handles the buffs
local function update_market_contents(market)
if (stone_tracker.previous_stone_sent_to_surface == stone_tracker.stone_sent_to_surface) then
return
end
local should_update_mining_speed = false
local should_update_inventory_slots = false
local should_update_stone_collecting = false
---Updates market content with new items if they are to be unlocked
---Primarily used by the force control system at every level up
---@param market LuaEntity The market to be updated
---@param force LuaForce the force which the unlocking requirement should be based of
function MarketExchange.update_market_contents(market, force)
local add_market_item
local old_level = stone_tracker.current_level
local print = game.print
local item_unlocked = false
if (calculate_level(stone_tracker.current_level+1) <= stone_tracker.stone_sent_to_surface) then
stone_tracker.current_level = stone_tracker.current_level + 1
end
for _, unlockable in pairs(config.unlockables) do
local stone_unlock = calculate_level(unlockable.level)
local is_in_range = stone_unlock > stone_tracker.previous_stone_sent_to_surface and stone_unlock <= stone_tracker.stone_sent_to_surface
local is_in_range = force_control.get_force_data(force).current_level == unlockable.level
-- only add the item to the market if it's between the old and new stone range
if (is_in_range and unlockable.type == 'market') then
@ -153,84 +60,18 @@ local function update_market_contents(market)
end
end
MarketExchange.update_gui()
if (old_level < stone_tracker.current_level) then
if item_unlocked then
print(prefix..'We have reached level ' .. stone_tracker.current_level .. '! New items are available from the market!')
else
print(prefix..'We have reached level ' .. stone_tracker.current_level .. '!')
end
for _, buffs in pairs(config.buffs) do
if (buffs.prototype.name == 'mining_speed') then
local value = buffs.prototype.value
Debug.print('Mining Foreman: Increased mining speed by ' .. value .. '%!')
should_update_mining_speed = true
mining_efficiency.market_modifier = mining_efficiency.market_modifier + (value / 100)
elseif (buffs.prototype.name == 'inventory_slot') then
local value = buffs.prototype.value
Debug.print('Mining Foreman: Increased inventory slots by ' .. value .. '!')
should_update_inventory_slots = true
inventory_slots.market_modifier = inventory_slots.market_modifier + value
elseif (buffs.prototype.name == 'stone_automation') then
local value = buffs.prototype.value
if (stone_tracker.current_level == 1) then
print('Mining Foreman: We can now automatically send stone to the surface from a chest below the market!')
else
Debug.print('Mining Foreman: We can now automatically send ' .. value .. ' more stones!')
end
should_update_stone_collecting = true
stone_collecting.market_modifier = stone_collecting.market_modifier + value
end
end
end
local force
if (should_update_mining_speed) then
force = force or game.forces.player
update_mining_speed(force)
end
if (should_update_inventory_slots) then
force = force or game.forces.player
update_inventory_slots(force)
end
if (should_update_stone_collecting) then
update_stone_collecting()
end
end
local function on_research_finished(event)
local force = game.forces.player
local current_modifier = mining_efficiency.research_modifier
local new_modifier = force.mining_drill_productivity_bonus * config.mining_speed_productivity_multiplier * 0.5
if (current_modifier == new_modifier) then
-- something else was researched
return
end
mining_efficiency.research_modifier = new_modifier
inventory_slots.research_modifier = force.mining_drill_productivity_bonus * 50 -- 1 per level
stone_collecting.research_modifier = force.mining_drill_productivity_bonus * 1250 -- 25 per level
update_inventory_slots(force)
update_mining_speed(force)
update_stone_collecting()
end
local function redraw_title(data)
data.frame.caption = utils.comma_value(stone_tracker.stone_sent_to_surface) .. ' ' .. config.currency_item .. ' sent to the surface'
local force_data = force_control.get_force_data('player')
data.frame.caption = utils.comma_value(force_data.total_experience) .. ' total experience earned!'
end
local function get_data(unlocks, stone, type)
local result = {}
for _, data in pairs(unlocks) do
if calculate_level(data.level) == stone and data.type == type then
if data.level == stone and data.type == type then
insert(result, data)
end
end
@ -259,23 +100,14 @@ local function redraw_heading(data, header)
end
local function redraw_progressbar(data)
local force_data = force_control.get_force_data('player')
local flow = data.market_progressbars
Gui.clear(flow)
-- progress bar for next level
local act_stone = (stone_tracker.current_level ~= 0) and calculate_level(stone_tracker.current_level) or 0
local next_stone = calculate_level(stone_tracker.current_level+1)
local range = next_stone - act_stone
local sent = stone_tracker.stone_sent_to_surface - act_stone
local percentage = (math.floor((sent / range)*1000))*0.001
percentage = (percentage < 0) and (percentage*-1) or percentage
apply_heading_style(flow.add({type = 'label', tooltip = 'Currently at level: ' .. stone_tracker.current_level .. '\nNext level at: ' .. utils.comma_value(next_stone) ..'\nRemaining stone: ' .. utils.comma_value(range - sent), name = 'Diggy.MarketExchange.Frame.Progress.Level', caption = 'Progress to next level:'}).style)
local level_progressbar = flow.add({type = 'progressbar', tooltip = percentage * 100 .. '% stone to next level'})
apply_heading_style(flow.add({type = 'label', tooltip = 'Currently at level: ' .. force_data.current_level .. '\nNext level at: ' .. utils.comma_value((force_data.total_experience - force_data.current_experience) + force_data.experience_level_up_cap) ..' xp\nRemaining xp: ' .. utils.comma_value(force_data.experience_level_up_cap - force_data.current_experience), name = 'Diggy.MarketExchange.Frame.Progress.Level', caption = 'Progress to next level:'}).style)
local level_progressbar = flow.add({type = 'progressbar', tooltip = floor(force_data.experience_percentage*100)*0.01 .. '% xp to next level'})
level_progressbar.style.width = 350
level_progressbar.value = percentage
level_progressbar.value = force_data.experience_percentage * 0.01
end
local function redraw_table(data)
@ -296,10 +128,10 @@ local function redraw_table(data)
-- create table
for i = 1, #config.unlockables do
if calculate_level(config.unlockables[i].level) ~= last_stone then
if config.unlockables[i].level ~= last_stone then
-- get items and buffs for each stone value
items = get_data(config.unlockables, calculate_level(config.unlockables[i].level), 'market')
items = get_data(config.unlockables, config.unlockables[i].level, 'market')
-- get number of rows
number_of_rows = max(#buffs, #items)
@ -311,8 +143,7 @@ local function redraw_table(data)
local level = item.level
-- 1st column
result[6] = calculate_level(level)
result[1] = 'Level ' ..level
result[1] = level
-- 3rd column
if items[j] ~= nil then
result[3] = '+ ' .. item.prototype.name
@ -337,23 +168,23 @@ local function redraw_table(data)
end
-- save lastStone
last_stone = calculate_level(config.unlockables[i].level)
last_stone = config.unlockables[i].level
end
-- print table
for _, unlockable in pairs(row) do
local is_unlocked = unlockable[6] <= stone_tracker.stone_sent_to_surface
local is_unlocked = unlockable[1] <= force_control.get_force_data('player').current_level
local list = market_scroll_pane.add {type = 'table', column_count = 2 }
list.style.horizontal_spacing = 16
local caption = ''
if unlockable[4] ~= true then
caption = unlockable[1]
caption = 'Level ' .. unlockable[1]
else
caption = ''
end
local tag_stone = list.add {type = 'label', name = tag_label_stone, caption = caption}
local tag_stone = list.add {type = 'label', name = tag_label_stone, caption = caption, tooltip = 'XP: ' .. utils.comma_value(Experience.calculate_level_xp(unlockable[1]))}
tag_stone.style.minimal_width = 100
local tag_items = list.add {type = 'label', name = tag_label_item, caption = unlockable[3]}
@ -378,30 +209,27 @@ local function redraw_buff(data) --! Almost equals to the redraw_table() functio
local buff_scroll_pane = data.buff_scroll_pane
Gui.clear(buff_scroll_pane)
local buffs = {}
local number_of_rows = 0
local buffs = Experience.get_buffs()
local row = {}
for i = 1, #config.buffs do
-- get items and buffs for each stone value
buffs = config.buffs
local i = 0
for k, v in pairs(buffs) do
i = i + 1
local result = {}
-- 1st column
result[1] = 'All levels'
-- 2nd column
if buffs[i].prototype.name == 'mining_speed' then
result[2] = '+ '.. buffs[i].prototype.value .. '% mining speed'
elseif buffs[i].prototype.name == 'inventory_slot' then
if buffs[i].prototype.value > 1 then
result[2] = '+ '.. buffs[i].prototype.value .. ' inventory slots'
if k == 'mining_speed' then
result[2] = '+ '.. v.value .. '% mining speed'
elseif k == 'inventory_slot' then
if v.value > 1 then
result[2] = '+ '.. v.value .. ' inventory slots'
else
result[2] = '+ '.. buffs[i].prototype.value .. ' inventory slot'
result[2] = '+ '.. v.value .. ' inventory slot'
end
elseif buffs[i].prototype.name == 'stone_automation' then
result[2] = '+ '.. buffs[i].prototype.value .. ' stones automatically sent'
elseif k == 'health_bonus' then
result[2] = '+ '.. v.value .. ' max health'
else
result[2] = 'Description missing: unknown buff. Please contact admin'
end
@ -437,34 +265,29 @@ local function redraw_buff(data) --! Almost equals to the redraw_table() functio
end
end
local function on_market_item_purchased(event)
if (1 ~= event.offer_index) then
---Interface for force control system to access the primary market
---@return LuaEntity the primary market (The one at spawn)
function MarketExchange.get_market()
local markets = game.surfaces.nauvis.find_entities_filtered({name = 'market', position = config.market_spawn_position, limit = 1})
if (#markets == 0) then
Debug.print_position(config.market_spawn_position, 'Unable to find a market')
return
end
local sum = config.stone_to_surface_amount * event.count
Game.print_player_floating_text(event.player_index, '-' .. sum .. ' stone', {r = 0.6, g = 0.55, b = 0.42})
send_stone_to_surface(sum)
update_market_contents(event.market)
return markets[1]
end
local function on_placed_entity(event)
local market = event.entity
if ('market' ~= market.name) then
if 'market' ~= market.name then
return
end
market.add_market_item({
price = {{config.currency_item, 50}},
offer = {type = 'nothing', effect_description = 'Send ' .. config.stone_to_surface_amount .. ' ' .. config.currency_item .. ' to the surface. To see the overall progress and rewards, click the market button in the menu.'}
})
update_market_contents(market)
end
function MarketExchange.get_extra_map_info(config)
return 'Market Exchange, trade your stone or send it to the surface'
return 'Market Exchange, come make a deal at the foreman\'s shop'
end
local function toggle(event)
@ -530,6 +353,7 @@ Gui.on_custom_close('Diggy.MarketExchange.Frame', function (event)
event.element.destroy()
end)
---Updates the market progress gui for every player that has it open
function MarketExchange.update_gui()
for _, p in ipairs(game.connected_players) do
local frame = p.gui.left['Diggy.MarketExchange.Frame']
@ -537,7 +361,6 @@ function MarketExchange.update_gui()
if frame and frame.valid then
local data = {player = p, trigger = 'update_gui'}
toggle(data)
--toggle(data)
end
end
end
@ -547,10 +370,9 @@ function MarketExchange.on_init()
surface = game.surfaces.nauvis,
position = config.market_spawn_position,
player_force = game.forces.player,
void_chest_tiles = config.void_chest_tiles,
})
update_mining_speed(game.forces.player)
end
--[[--
@ -559,91 +381,10 @@ end
function MarketExchange.register(cfg)
config = cfg
Event.add(defines.events.on_research_finished, on_research_finished)
Event.add(defines.events.on_market_item_purchased, on_market_item_purchased)
--Events
Event.add(Template.events.on_placed_entity, on_placed_entity)
Event.add(defines.events.on_player_created, on_player_created)
local x_min
local y_min
local x_max
local y_max
for _, position in pairs(config.void_chest_tiles) do
local x = position.x
local y = position.y
if (nil == x_min or x < x_min) then
x_min = x
end
if (nil == x_max or x > x_max) then
x_max = x
end
if (nil == y_min or y < y_min) then
y_min = y
end
if (nil == y_max or y > y_max) then
y_max = y
end
end
local area = {{x_min, y_min}, {x_max + 1, y_max + 1}}
local message_x = (x_max + x_min) * 0.5
local message_y = (y_max + y_min) * 0.5
Event.on_nth_tick(config.void_chest_frequency, function ()
local send_to_surface = 0
local surface = game.surfaces.nauvis
local find_entities_filtered = surface.find_entities_filtered
local chests = find_entities_filtered({area = area, type = {'container', 'logistic-container'}})
local to_fetch = stone_collecting.active_modifier
for _, chest in pairs(chests) do
local chest_contents = chest.get_inventory(defines.inventory.chest)
local stone_in_chest = chest_contents.get_item_count(config.currency_item)
local delta = to_fetch
if (stone_in_chest < delta) then
delta = stone_in_chest
end
if (delta > 0) then
chest_contents.remove({name = config.currency_item, count = delta})
send_to_surface = send_to_surface + delta
end
end
if (send_to_surface == 0) then
if (0 == to_fetch) then
return
end
local message = 'Missing chests below market'
if (#chests > 0) then
message = 'No stone in chests found'
end
Game.print_floating_text(surface, {x = message_x, y = message_y}, message, { r = 220, g = 100, b = 50})
return
end
local markets = find_entities_filtered({name = 'market', position = config.market_spawn_position, limit = 1})
if (#markets == 0) then
Debug.print_position(config.market_spawn_position, 'Unable to find a market')
return
end
local message = send_to_surface .. ' stone sent to the surface'
Game.print_floating_text(surface, {x = message_x, y = message_y}, message, { r = 0.6, g = 0.55, b = 0.42})
send_stone_to_surface(send_to_surface)
update_market_contents(markets[1])
end)
Event.on_nth_tick(61, MarketExchange.update_gui)
end
return MarketExchange

View File

@ -1,54 +0,0 @@
-- dependencies
local Config = require 'map_gen.Diggy.Config'.features.MarketExchange
-- this
local MarketUnlockables = {}
local floor = math.floor
local ceil = math.ceil
local log10 = math.log10
--- Handles a truncation of numbers to create simple large numbers
-- eg. 1593251 could become 1590000 with precision 3
-- number larger than 10 million will have an precision of +1
-- @param precision number of the precision wanted (number of significant digits)
-- @param precise_number number that needs to be truncated/simplified
--
local function truncate(precision, precise_number)
local number = precise_number
local number_length = floor(log10(number)+1)
precision = (number_length >= 8) and (precision+1) or precision
local exponent = number_length -precision
number = number/10^exponent
number = floor(number)*10^exponent
return number, number_length
end
--- Handles the level requirement to stone sent. Calculates based on a formula one number corresponding to that levels cost
-- You can configure this in Diggy.Config.lua under features.MarketExchange
-- @param level number of a level
-- @returns number of cost corresponding to the level based on a calculation
--
function MarketUnlockables.calculate_level(level) -- all configurable variables must be integers.
local b = floor(Config.difficulty_scale) or 25 -- Default 25 <-- Controls how much stone is needed.
local start_value = floor(Config.start_stone) or 50 -- The start value/the first level cost
local formula = b*(level^3)+(start_value-b)
local precision = floor(Config.cost_precision) or 2 -- Sets the precision
-- Truncates to the precision and prevents duplicates by incrementing with 5 in the third highest place.
-- First evaluates loosely if the previous level requirement would return same number after truncating.
-- If true evaluates down all levels to level 1 for the precise number
-- (In case itself got incremented)
-- Only useful if three or more values turns out to be the same after truncating, thus the loosely evaluation to save an expensive recursive function
local number, number_lenght = truncate(precision, formula)
local prev_number = truncate(precision, b*((level-1)^3)+(start_value-b))
if (level ~= 1 and number == prev_number) then
local prev_number = MarketUnlockables.calculate_level((level-1))
while (prev_number >= number) do
number = (prev_number < number) and number or ceil(number + (5*10^(number_lenght -3)))
end
end
return number
end
return MarketUnlockables

View File

@ -68,7 +68,6 @@ end
--[[
Creates a floating text entity at the player location with the specified color in {r, g, b} format.
Example: "+10 iron" or "-10 coins"
@param text String to display
@ -76,14 +75,19 @@ end
@return the created entity
]]
function Game.print_player_floating_text(player_index, text, color)
function Game.print_player_floating_text_position(player_index, text, color, x_offset, y_offset)
local player = Game.get_player_by_index(player_index)
if not player or not player.valid then
return
end
local position = player.position
return Game.print_floating_text(player.surface, {x = position.x, y = position.y - 1.5}, text, color)
return Game.print_floating_text(player.surface, {x = position.x + x_offset, y = position.y + y_offset}, text, color)
end
function Game.print_player_floating_text(player_index, text, color)
Game.print_player_floating_text_position(player_index, text, color, 0, -1.5)
end
return Game