skill update part 2 (#22956)

* skill issue

* more stuff

* not so much
This commit is contained in:
SapphicOverload
2025-01-19 00:11:31 -05:00
committed by GitHub
parent 1d8e4c9316
commit 827e14bbda
19 changed files with 206 additions and 46 deletions

View File

@@ -25,3 +25,6 @@
/// Experience required to increase your skills by one level. Increases exponentially the higher your level already is.
#define EXPERIENCE_PER_LEVEL 500
/// Calculates how much experience is required to reach a given level.
#define EXP_REQ_CALC(level) (EXPERIENCE_PER_LEVEL * (((2**(level + 1)) / 2) - 1))

View File

@@ -155,7 +155,7 @@
data["skills"] = skill_data
data["skill_points"] = user.mind.skill_points
data["allocated_points"] = allocated_points
data["exceptional_skill"] = HAS_MIND_TRAIT(user, TRAIT_EXCEPTIONAL_SKILL)
data["skill_cap"] = EXP_MASTER + HAS_MIND_TRAIT(user, TRAIT_EXCEPTIONAL_SKILL)
return data
/atom/movable/screen/skill_menu/ui_static_data(mob/user)
@@ -199,7 +199,7 @@
return TRUE
if(allocated_points + params["amount"] < 0)
return TRUE
if(allocated_skills[params["skill"]] + params["amount"] + user.get_skill(params["skill"]) > (4 + HAS_MIND_TRAIT(user, TRAIT_EXCEPTIONAL_SKILL)))
if(allocated_skills[params["skill"]] + params["amount"] + user.get_skill(params["skill"]) > (EXP_MASTER + HAS_MIND_TRAIT(user, TRAIT_EXCEPTIONAL_SKILL)))
return TRUE
if(allocated_skills[params["skill"]] + params["amount"] < 0)
return TRUE

View File

@@ -59,7 +59,7 @@
SKILL_FITNESS = EXP_NONE,
)
/// Progress towards increasing their skill level
/// Progress towards increasing their skill level.
var/list/exp_progress = list(
SKILL_PHYSIOLOGY = 0,
SKILL_MECHANICAL = 0,
@@ -68,6 +68,9 @@
SKILL_FITNESS = 0,
)
/// One-time experience gains that have already been acquired.
var/list/exp_sources = list()
/// Free skill points to allocate
var/skill_points = 0
@@ -118,6 +121,7 @@
key = _key
soulOwner = src
martial_art = default_martial_art
RegisterSignal(src, SIGNAL_ADDTRAIT(TRAIT_EXCEPTIONAL_SKILL), PROC_REF(update_skills))
/datum/mind/Destroy()
SSticker.minds -= src
@@ -804,25 +808,47 @@
return FALSE
return (mind.skills[skill] >= amount)
/// Adds progress towards increasing skill level. Returns TRUE if it improved the skill.
/mob/proc/add_exp(skill, amount)
/// Adds progress towards increasing skill level. Returns TRUE if it added progress. Adding a source prevents gaining exp from that source again.
/mob/proc/add_exp(skill, amount, source)
if(!mind)
return FALSE
if(mind.skill_points > 0)
if(!amount)
return FALSE
var/exp_required = EXPERIENCE_PER_LEVEL * (2**mind.skills[skill]) // exp required scales exponentially
if(mind.exp_progress[skill] + amount >= exp_required)
var/levels_gained = round(log(2, 1 + (mind.exp_progress[skill] + amount) / exp_required)) // in case you gained so much you go up more than one level
var/levels_allocated = hud_used?.skill_menu ? hud_used.skill_menu.allocated_skills[skill] : 0
if(levels_allocated > 0) // adjust any already allocated skills to prevent shenanigans (you know who you are)
hud_used.skill_menu.allocated_points -= min(levels_gained, levels_allocated)
hud_used.skill_menu.allocated_skills[skill] -= min(levels_gained, levels_allocated)
mind.exp_progress[skill] += amount - exp_required * (2**(levels_gained - 1))
adjust_skill(skill, levels_gained)
to_chat(src, span_boldnotice("Your [skill] skill is now level [get_skill(skill)]!"))
return TRUE
if(source && (source in mind.exp_sources))
return FALSE
mind.exp_sources.Add(source)
mind.exp_progress[skill] += amount
return FALSE
var/levels_gained = check_exp(skill)
if(levels_gained) // remove an equal amount of unallocated skill points to prevent exploits
mind.skill_points = max(mind.skill_points - levels_gained, 0)
return TRUE
/// Levels up a skill if it has enough experience to do so.
/mob/proc/check_exp(skill)
if(!mind)
return FALSE
var/current_level = get_skill(skill)
var/exp_required = EXPERIENCE_PER_LEVEL * (2**current_level) // exp required scales exponentially
if(mind.exp_progress[skill] < exp_required)
return FALSE
var/skill_cap = EXP_MASTER + HAS_MIND_TRAIT(src, TRAIT_EXCEPTIONAL_SKILL)
var/levels_gained = min(round(log(2, 1 + (mind.exp_progress[skill] / exp_required))), max(skill_cap - current_level)) // in case you gained so much you go up more than one level
if(levels_gained < 1)
return FALSE
var/levels_allocated = hud_used?.skill_menu ? hud_used.skill_menu.allocated_skills[skill] : 0
if(levels_allocated > 0) // adjust any already allocated skills to prevent shenanigans (you know who you are)
hud_used.skill_menu.allocated_points -= min(levels_gained, levels_allocated)
hud_used.skill_menu.allocated_skills[skill] -= min(levels_gained, levels_allocated)
mind.exp_progress[skill] -= exp_required * (((2**round(levels_gained + 1)) / 2) - 1)
adjust_skill(skill, levels_gained, max_skill = skill_cap)
to_chat(src, span_boldnotice("Your [skill] skill is now level [get_skill(skill)]!"))
return levels_gained
/// Returns whether experience has been gained from a given source.
/mob/proc/has_exp(source)
if(!mind)
return FALSE
return (source in mind.exp_sources) ? TRUE : FALSE
/// Adds skill points to be allocated at will.
/mob/proc/add_skill_points(amount)
@@ -831,6 +857,14 @@
mind.skill_points += amount
throw_alert("skill points", /atom/movable/screen/alert/skill_up)
/// Called when [TRAIT_EXCEPTIONAL_SKILL] is added to the mob.
/datum/mind/proc/update_skills(datum/source)
SIGNAL_HANDLER
if(!current)
return
for(var/skill in skills)
current.check_exp(skill)
/datum/mind/proc/has_martialart(string)
if(martial_art && martial_art.id == string)
return martial_art

View File

@@ -184,13 +184,15 @@
return TRUE
return FALSE
/datum/wires/proc/cut(wire)
/datum/wires/proc/cut(wire, mob/user)
if(is_cut(wire))
cut_wires -= wire
on_cut(wire, mend = TRUE)
else
cut_wires += wire
on_cut(wire, mend = FALSE)
if(user)
user.add_exp(SKILL_TECHNICAL, 50, "[wire]_[type]")
/datum/wires/proc/cut_color(color)
cut(get_wire(color))
@@ -202,10 +204,12 @@
for(var/wire in wires)
cut(wire)
/datum/wires/proc/pulse(wire, user)
/datum/wires/proc/pulse(wire, mob/user)
if(is_cut(wire))
return
on_pulse(wire, user)
if(user)
user.add_exp(SKILL_TECHNICAL, 50, "[wire]_[type]")
/datum/wires/proc/pulse_color(color, mob/living/user)
pulse(get_wire(color), user)

View File

@@ -1380,6 +1380,6 @@ GLOBAL_VAR_INIT(year_integer, text2num(year)) // = 2013???
if(!pilot)
pilot = occupant
var/effective_skill = pilot.get_skill(SKILL_TECHNICAL)
if(effective_skill < EXP_MASTER && HAS_TRAIT(pilot, TRAIT_SKILLED_PILOT))
effective_skill += EXP_LOW // mech pilot suit adds extra pilot skill, up to EXP_MASTER
return (12 - effective_skill) / 10
if(HAS_TRAIT(pilot, TRAIT_SKILLED_PILOT) || HAS_MIND_TRAIT(pilot, TRAIT_SKILLED_PILOT))
effective_skill += EXP_LOW
return 1 + (2 - effective_skill) * 0.075

View File

@@ -8,6 +8,8 @@
unique = 1
/// Flavor messages displayed to mobs reading the granter
var/list/remarks = list()
/// Whether to display the remarks in order.
var/ordered_remarks = FALSE
/// Controls how long a mob must keep the book in his hand to actually successfully learn
var/pages_to_mastery = 3
/// Sanity, whether it's currently being read
@@ -40,7 +42,7 @@
on_reading_start(user)
reading = TRUE
for(var/i in 1 to pages_to_mastery)
if(!turn_page(user))
if(!turn_page(user, i))
on_reading_stopped()
reading = FALSE
return
@@ -64,13 +66,13 @@
to_chat(user, span_notice("You finish reading [name]!"))
/// The actual "turning over of the page" flavor bit that happens while someone is reading the granter.
/obj/item/book/granter/proc/turn_page(mob/living/user)
/obj/item/book/granter/proc/turn_page(mob/living/user, current_page = 1)
playsound(user, pick(book_sounds), 30, TRUE)
if(!do_after(user, 5 SECONDS, src))
return FALSE
to_chat(user, span_notice("[length(remarks) ? pick_n_take(remarks) : "You keep reading..."]"))
to_chat(user, span_notice("[ordered_remarks ? "[remarks[current_page]]" : (length(remarks) ? pick_n_take(remarks) : "You keep reading...")]"))
return TRUE
/// Effects that occur whenever the book is read when it has no uses left.

View File

@@ -101,6 +101,7 @@
icon = 'icons/obj/module.dmi'
icon_state = "cyborg_upgrade"
remarks = list("MANKIND IS DEAD.", "BLOOD IS FUEL.", "HELL IS FULL.")
ordered_remarks = TRUE
/obj/item/book/granter/martial/ultra_violence/on_reading_start(mob/user)
to_chat(user, span_notice("You plug \the [src] in and begin loading PRG$[martial_name]."))

View File

@@ -14,4 +14,4 @@
/obj/item/book/granter/mechpiloting/on_reading_finished(mob/user)
. = ..()
user.adjust_skill(SKILL_TECHNICAL, EXP_MID, max_skill = EXP_GENIUS)
ADD_TRAIT(user.mind, TRAIT_SKILLED_PILOT, type)

View File

@@ -0,0 +1,77 @@
/// Grants experience to the reader.
/obj/item/book/granter/skill
name = "skill guide"
desc = "A guide to getting good, whatever that means."
remarks = list(
"Skill issue...?",
)
/// Experience gains from reading this book.
var/list/exp_gain = list(
SKILL_PHYSIOLOGY = EXPERIENCE_PER_LEVEL,
SKILL_MECHANICAL = EXPERIENCE_PER_LEVEL,
SKILL_TECHNICAL = EXPERIENCE_PER_LEVEL,
SKILL_SCIENCE = EXPERIENCE_PER_LEVEL,
SKILL_PHYSIOLOGY = EXPERIENCE_PER_LEVEL,
)
/obj/item/book/granter/skill/can_learn(mob/living/user)
return !user.has_exp("[type]_[exp_gain[1]]")
/obj/item/book/granter/skill/on_reading_finished(mob/living/user)
. = ..()
if(!user.mind)
CRASH("[user.type] somehow read [type] without a mind!")
for(var/skill in exp_gain)
user.add_exp(skill, exp_gain[skill], "[type]_[skill]")
/obj/item/book/granter/skill/physiology
name = "\improper Guide to First Aid"
desc = "This book teaches basic first aid information."
remarks = list(
"Dying is bad..?",
"Suture or cauterize open wounds to prevent bleeding out...",
"Apply ointment or regenerative mesh to sterilize infected burns...",
"Move critical patients on rolling beds or over your shoulder..."
)
exp_gain = list(
SKILL_PHYSIOLOGY = EXP_REQ_CALC(EXP_HIGH),
)
/obj/item/book/granter/skill/mechanics
name = "Nuclear Engineering for Dummies"
desc = "A step-by-step guide to operating a nuclear reactor."
remarks = list(
"Wear radiation protection during maintenance...",
"Adjust control rods to moderate the temperature...",
"High temperatures generate more power...",
"Don't press AZ-5..?",
)
exp_gain = list(
SKILL_MECHANICAL = EXP_REQ_CALC(EXP_HIGH),
)
/obj/item/book/granter/skill/technical
name = "Hacking 101"
desc = "Contains detailed information on airlock maintenance."
remarks = list(
"Wear insulated gloves for protection...",
"Pulse wires twice to avoid changing settings...",
"Pry open unpowered doors with a crowbar...",
"Bolt an open door to prevent it closing behind you...",
)
exp_gain = list(
SKILL_TECHNICAL = EXP_REQ_CALC(EXP_HIGH),
)
/obj/item/book/granter/skill/science
name = "Statistical Mechanics and Thermodynamics"
desc = "Perhaps it will be wise to approach this subject cautiously."
remarks = list(
"Ludwig Boltzmann, who spent much of his life studying statistical mechanics, died in 1906, by his own hand...",
"Paul Ehrenfest, carrying on the work, died similarly in 1933...",
"Now it is our turn to study statistical mechanics...",
)
ordered_remarks = TRUE
exp_gain = list(
SKILL_SCIENCE = EXP_REQ_CALC(EXP_HIGH),
)

View File

@@ -284,7 +284,7 @@
if(usr.incapacitated())
return
for(var/obj/item/O in contents)
seedify(O, 1)
seedify(O, 1, null, usr)
// -----------------------------
// Stack Snatcher

View File

@@ -255,7 +255,9 @@
/obj/machinery/plantgenes/Topic(href, list/href_list)
if(..())
return
usr.set_machine(src)
var/mob/user = usr
user.set_machine(src)
if(href_list["eject_seed"] && !operation)
if (seed)
@@ -338,6 +340,9 @@
gene.value = max(gene.value, min_wrate)
else if(istype(G, /datum/plant_gene/core/weed_chance))
gene.value = max(gene.value, min_wchance)
if(G.extract_value && user.add_exp(SKILL_SCIENCE, G.extract_value, G.type))
user.playsound_local(get_turf(src), 'sound/machines/ping.ogg', 25, TRUE)
balloon_alert(user, "new trait catalogued: [lowertext(G.name)]")
disk.update_appearance()
qdel(seed)
seed = null

View File

@@ -1,5 +1,7 @@
/datum/plant_gene
var/name
/// Skill gain from extracting this gene. Can only be gained once.
var/extract_value = 0
/datum/plant_gene/proc/get_name() // Used for manipulator display and gene disk name.
return name
@@ -138,6 +140,7 @@
var/rate = 0.05
var/examine_line = ""
var/trait_id // must be set and equal for any two traits of the same type
extract_value = 150
/datum/plant_gene/trait/Copy()
var/datum/plant_gene/trait/G = ..()
@@ -433,6 +436,7 @@
/datum/plant_gene/trait/fire_resistance // Lavaland
name = "Fire Resistance"
extract_value = 500
/datum/plant_gene/trait/fire_resistance/apply_vars(obj/item/seeds/S)
if(!(S.resistance_flags & FIRE_PROOF))
@@ -454,3 +458,4 @@
/datum/plant_gene/trait/plant_type/alien_properties
name ="?????"
extract_value = 500

View File

@@ -22,7 +22,7 @@
else
t_max = rand(1,4)
var/seedloc = O.loc
var/atom/seedloc = O.loc
if(extractor)
seedloc = extractor.loc
@@ -37,7 +37,6 @@
t_prod.forceMove(seedloc)
t_amount++
qdel(O)
return seeds
else if(istype(O, /obj/item/grown))
var/obj/item/grown/F = O
@@ -46,13 +45,18 @@
return
while(t_amount < t_max)
var/obj/item/seeds/t_prod = F.seed.Copy()
seeds.Add(t_prod)
t_prod.forceMove(seedloc)
t_amount++
qdel(O)
return 1
return 0
if(user && seeds.len)
var/obj/item/seeds/seed = seeds[1] // all seeds are duplicates, pick the first one in the list
if(user.add_exp(SKILL_SCIENCE, seed.rarity * 10, seed.type))
user.playsound_local(get_turf(seedloc), 'sound/machines/ping.ogg', 25, TRUE)
seedloc.balloon_alert(user, "rare plant catalogued: [initial(seed.product.name)]")
return (seeds.len ? seeds : FALSE)
/obj/machinery/seed_extractor
name = "seed extractor"

View File

@@ -9,7 +9,7 @@
resistance_flags = FLAMMABLE
var/plantname = "Plants" // Name of plant when planted.
var/plantdesc // Description of plant when planted
var/product // A type path. The thing that is created when the plant is harvested.
var/atom/product // A type path. The thing that is created when the plant is harvested.
var/species = "" // Used to update icons. Should match the name in the sprites unless all icon_* are overridden.
var/growing_icon = 'icons/obj/hydroponics/growing.dmi' //the file that stores the sprites of the growing plant from this seed.

View File

@@ -199,7 +199,6 @@
var/unique = 0 //0 - Normal book, 1 - Should not be treated as normal book, unable to be copied, unable to be modified
var/title //The real name of the book.
var/window_size = null // Specific window size for the book, i.e: "1920x1080", Size x Width
var/list/skill_gain
/obj/item/book/attack_self(mob/user)

View File

@@ -40,14 +40,16 @@
return
if(istype(W, /obj/item/slime_extract))
refine_plort(W)
qdel(W)
refine_plort(W, user)
return
/obj/machinery/plortrefinery/proc/refine_plort(obj/item/slime_extract/W)
point_gain = W.plort_value * research_point_multiplier
linked_techweb.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, point_gain)
/obj/machinery/plortrefinery/proc/refine_plort(obj/item/slime_extract/extract, mob/user)
point_gain = extract.plort_value * research_point_multiplier
linked_techweb.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, point_gain)
if(user.add_exp(SKILL_SCIENCE, extract.plort_value * 5, extract.type))
user.playsound_local(get_turf(src), 'sound/machines/ping.ogg', 25, TRUE)
balloon_alert(user, "new sample processed: [extract.effectmod]")
qdel(extract)
/obj/machinery/plortrefinery/Initialize(mapload)
. = ..()

View File

@@ -2113,6 +2113,29 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
item = /obj/item/book/granter/mechpiloting
cost = 5 //this is genuinely a REALLY strong effect, don't sleep on it
/datum/uplink_item/device_tools/physiology_guide
name = "Guide to First Aid"
desc = "A book that improves the reader's physiological knowledge."
item = /obj/item/book/granter/skill/physiology
cost = 3
/datum/uplink_item/device_tools/mechanics_guide
name = "Nuclear Engineering for Dummies"
desc = "A book that improves the reader's mechanical skills."
item = /obj/item/book/granter/skill/mechanics
cost = 3
/datum/uplink_item/device_tools/technical_guide
name = "Hacking 101"
desc = "A book that improves the reader's technical abilities."
item = /obj/item/book/granter/skill/technical
cost = 3
/datum/uplink_item/device_tools/science_guide
name = "Statistical Mechanics and Thermodynamics"
desc = "A book that improves the reader's scientific proficiency."
item = /obj/item/book/granter/skill/science
cost = 3
/datum/uplink_item/device_tools/mech_drop
name = "Orbital Mech Drop Fulton"

View File

@@ -73,7 +73,7 @@ export const SkillMenu = (props, context) => {
const AdjustSkill = (props, context) => {
const { act, data } = useBackend(context);
const { skill, name, index, tooltip, color } = props;
const { skills, skill_points, allocated_points, exceptional_skill, exp_per_level } = data;
const { skills, skill_points, allocated_points, skill_cap, exp_per_level } = data;
const { base, allocated, exp_progress } = skills[index];
const exp_required = exp_per_level * Math.pow(2, base);
@@ -100,25 +100,25 @@ const AdjustSkill = (props, context) => {
amount: -1,
})}
/>
{base + allocated} / {exceptional_skill ? 5 : 4}
{base + allocated} / {skill_cap}
<Button
ml="2px"
icon="plus"
disabled={(base + allocated) >= (exceptional_skill ? 5 : 4) || allocated_points >= skill_points}
disabled={(base + allocated) >= skill_cap || allocated_points >= skill_points}
onClick={() => act('allocate', {
skill: skill,
amount: 1,
})}
/>
<ProgressBar
value={exp_progress}
value={(base >= skill_cap) ? exp_required : exp_progress}
maxValue={exp_required}
color={color}
width={7.5}
ml={1}
textAlign="left"
>
{Math.floor(exp_progress)} / {Math.floor(exp_required)}
{(base >= skill_cap) ? "Max" : (Math.floor(exp_progress) + " / " + Math.floor(exp_required))}
</ProgressBar>
</Box>
</LabeledList.Item>

View File

@@ -1387,6 +1387,7 @@
#include "code\game\objects\items\granters\_granters.dm"
#include "code\game\objects\items\granters\mech_piloting.dm"
#include "code\game\objects\items\granters\origami.dm"
#include "code\game\objects\items\granters\skills.dm"
#include "code\game\objects\items\granters\crafting\_crafting_granter.dm"
#include "code\game\objects\items\granters\crafting\ashwalkers.dm"
#include "code\game\objects\items\granters\crafting\bone_notes.dm"