From c4b36a5490959f2a99043cd42004ad31e4dcd712 Mon Sep 17 00:00:00 2001 From: NanakoAC Date: Wed, 26 Oct 2016 16:22:39 +0100 Subject: [PATCH] Sprinting, Stamina and Temporary Modifiers (#1030) Introduces: - Temporary modifiers process and datums for that - A new stamina and sprinting mechanic. That is in need of further development! Weee! --- baystation12.dme | 3 + code/__HELPERS/icons.dm | 13 + code/__HELPERS/unsorted.dm | 20 + code/_onclick/hud/screen_objects.dm | 34 +- code/controllers/Processes/modifier.dm | 22 + code/game/atoms.dm | 2 +- code/game/modifiers/modifiers.dm | 452 ++++++++++++++++++ code/game/modifiers/modifiers_chem.dm | 73 +++ code/game/objects/items.dm | 45 ++ code/game/objects/objs.dm | 1 + code/game/turfs/turf.dm | 7 +- code/global.dm | 1 + code/modules/mob/living/carbon/human/human.dm | 6 + .../mob/living/carbon/human/human_damage.dm | 2 +- .../mob/living/carbon/human/human_movement.dm | 15 +- .../mob/living/carbon/human/intoxication.dm | 47 ++ code/modules/mob/living/carbon/human/life.dm | 105 ++-- .../carbon/human/species/outsider/skeleton.dm | 5 + .../carbon/human/species/outsider/vox.dm | 6 + .../living/carbon/human/species/species.dm | 60 ++- .../carbon/human/species/station/golem.dm | 5 + .../carbon/human/species/station/station.dm | 80 ++++ code/modules/mob/living/living_defines.dm | 14 +- code/modules/mob/living/living_powers.dm | 41 +- code/modules/mob/mob_defines.dm | 2 +- code/modules/mob/mob_movement.dm | 31 +- code/modules/reagents/Chemistry-Reagents.dm | 9 + .../Chemistry-Reagents-Dispenser.dm | 6 + .../Chemistry-Reagents-Food-Drinks.dm | 17 + .../Chemistry-Reagents-Medicine.dm | 13 +- html/changelogs/Nanako-Sprinting.yml | 40 ++ icons/mob/screen1.dmi | Bin 168715 -> 169472 bytes 32 files changed, 1097 insertions(+), 80 deletions(-) create mode 100644 code/controllers/Processes/modifier.dm create mode 100644 code/game/modifiers/modifiers.dm create mode 100644 code/game/modifiers/modifiers_chem.dm create mode 100644 html/changelogs/Nanako-Sprinting.yml diff --git a/baystation12.dme b/baystation12.dme index b9d3fd157d8..82d769a07f9 100644 --- a/baystation12.dme +++ b/baystation12.dme @@ -111,6 +111,7 @@ #include "code\controllers\Processes\inactivity.dm" #include "code\controllers\Processes\machinery.dm" #include "code\controllers\Processes\mob.dm" +#include "code\controllers\Processes\modifier.dm" #include "code\controllers\Processes\nanoui.dm" #include "code\controllers\Processes\obj.dm" #include "code\controllers\Processes\Shuttle.dm" @@ -525,6 +526,8 @@ #include "code\game\mecha\working\hoverpod.dm" #include "code\game\mecha\working\ripley.dm" #include "code\game\mecha\working\working.dm" +#include "code\game\modifiers\modifiers.dm" +#include "code\game\modifiers\modifiers_chem.dm" #include "code\game\objects\buckling.dm" #include "code\game\objects\empulse.dm" #include "code\game\objects\explosion.dm" diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm index 7468db758b5..6dc41693da0 100644 --- a/code/__HELPERS/icons.dm +++ b/code/__HELPERS/icons.dm @@ -853,3 +853,16 @@ proc/sort_atoms_by_layer(var/list/atoms) result.Swap(i, gap + i) swapped = 1 return result + +proc/percentage_to_colour(var/P) + //Takes a value between 0-1 + //Returns a colour - pure green if 1, pure red if 0 + //Inbetween values will gradiant through green, yellow, orange, red + + + var/green = min(1, P*2)*255 + var/red = 255 - (min(1, (P-0.5)*2)*255) + //var/green = (max(0, P-0.5)*2)*255 + //var/red = 255 - (min(1, P*2)*255) + + return rgb(red,green,0) diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index b56fceb9215..d570f572b82 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -1370,3 +1370,23 @@ var/mob/dview/dview_mob = new // call to generate a stack trace and print to runtime logs /proc/crash_with(msg) CRASH(msg) + +/atom/proc/find_up_hierarchy(var/atom/target) + //This function will recurse up the hierarchy containing src, in search of the target + //It will stop when it reaches an area, as areas have no loc + var/x = 0//As a safety, we'll crawl up a maximum of ten layers + var/atom/a = src + while (x < 10) + x++ + if (isnull(a)) + return 0 + + if (a == target)//we found it! + return 1 + + if (istype(a, /area)) + return 0//Can't recurse any higher than this. + + a = a.loc + + return 0//If we get here, we must be buried many layers deep in nested containers. Shouldn't happen diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm index c638c03a57c..1d6adf91643 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -233,8 +233,17 @@ L.resist() if("mov_intent") + + var/list/modifiers = params2list(params) + //This is here instead of outside the switch to save adding overhead. its not used for any other UI icons right now + //If its needed in future for other UI icons, then move it up to above the switch + if(iscarbon(usr)) var/mob/living/carbon/C = usr + if (modifiers["alt"]) + C.set_walk_speed() + return + if(C.legcuffed) C << "You are legcuffed! You cannot run until you get [C.legcuffed] removed!" C.m_intent = "walk" //Just incase @@ -243,10 +252,9 @@ switch(usr.m_intent) if("run") usr.m_intent = "walk" - usr.hud_used.move_intent.icon_state = "walking" if("walk") usr.m_intent = "run" - usr.hud_used.move_intent.icon_state = "running" + update_move_icon(usr) if("m_intent") if(!usr.m_int) switch(usr.m_intent) @@ -589,3 +597,25 @@ usr.update_inv_r_hand(0) usr.next_move = world.time+6 return 1 + + + + + +//This updates the run/walk button on the hud +/obj/screen/proc/update_move_icon(var/mob/living/user) + switch(name) + if("mov_intent") + overlays.Cut() + switch(user.m_intent) + if("run")//When in run mode, the button will have a flashing coloured overlay which gives a visual indicator of stamina + icon_state = "running" + if (user.max_stamina != -1)//If max stamina is -1, this species doesnt use stamina. no overlay for them + var/image/holder = image('icons/mob/screen1.dmi', src, "run_overlay") + var/staminaportion = user.stamina / user.max_stamina + holder.color = percentage_to_colour(staminaportion) + holder.blend_mode = BLEND_MULTIPLY + overlays += holder + + if("walk") + icon_state = "walking" diff --git a/code/controllers/Processes/modifier.dm b/code/controllers/Processes/modifier.dm new file mode 100644 index 00000000000..7bbb181d120 --- /dev/null +++ b/code/controllers/Processes/modifier.dm @@ -0,0 +1,22 @@ +/datum/controller/process/modifier/setup() + name = "modifiers" + schedule_interval = 10 + start_delay = 8 + +/datum/controller/process/modifier/started() + ..() + if(!processing_modifiers) + processing_modifiers = list() + +/datum/controller/process/modifier/doWork() + for(last_object in processing_modifiers) + var/datum/modifier/O = last_object + if(isnull(O.gcDestroyed)) + O.process() + else + catchBadType(O) + processing_objects -= O + +/datum/controller/process/modifier/statProcess() + ..() + stat(null, "[processing_modifiers.len] modifiers") diff --git a/code/game/atoms.dm b/code/game/atoms.dm index f642cdc782c..8c84be66479 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -16,7 +16,7 @@ var/germ_level = GERM_LEVEL_AMBIENT // The higher the germ level, the more germ on the atom. var/simulated = 1 //filter for actions - used by lighting overlays var/fluorescent // Shows up under a UV light. - + var/list/modifiers = list() ///Chemistry. var/datum/reagents/reagents = null diff --git a/code/game/modifiers/modifiers.dm b/code/game/modifiers/modifiers.dm new file mode 100644 index 00000000000..7c14dc81da3 --- /dev/null +++ b/code/game/modifiers/modifiers.dm @@ -0,0 +1,452 @@ +/* + +//Temporary modifiers system, by Nanako + +//This system is designed to allow making non-permanant, reversible changes to variables of any atom, +//Though the system will be primarily used for altering properties of mobs, it can work on anything. + +//Intended uses are to allow equipment and items that modify a mob's various stats and attributes +//As well as to replace the badly designed chem effects system + + +This system works through a few main procs which should be overridden: +All overridden procs should contain a call to parent at the start, before any other code + + +Activate: This applies the effects. its here that you change any variables. + The author is also responsible here for storing any information necessary to later revert these changes + +Deactivate: This proc removes the effects. The author is responsible for writing it to reverse the changes cleanly + If using a strength var or any other kind of dynamic determinor of effects + It is very important NOT to factor that in when deactivating, because it may be changed while active + Instead, factor it in while activating and save the delta of the changed values. + that is, how much you added/subtracted + Apply that saved value in reverse when deactivating. + +Both activate and deactivate are stateful. Activate will not run if active is true. +Deactivate will not run if active is false + +Process: Called once every second while the effect is active. Usually this will only see if its time + to recheck validity, but it can be overridden to add extra per-tick functionality. + +When created, a status effect will take the following parameters in new + Mandatory: + 1. Affected atom + 2. Modifier type + 3. Source atom (not mandatory if type is custom) + + Optional: + 4. Source data + 5. Strength + 6. Duration + 7. Check interval + + + +//The affected atom is mandatory, without something to affect the modifier cannot exist + +//Modifier type is one of a selection of constants which determines the automated validity checking. + It does not enforce anything about the changes or other functionality. A valid option is mandatory + +//Source object is the thing that this modifier is based on, or anchored to. + //It is used as a point of reference in validity checks. Usually mandatory but some types do not require it + +//Source data provides additional information for validity, such as a maximum range from the source. + //Only required for certain types + +//Strength can be passed in by the caller and used insetting or removing the variable changes. + //It is never required for anything and not incorporated in base behaviour + +//Duration can be used for any type except custom. The modifier will cease to be valid + //this long after it is created + //Duration is decremented every proc until it falls below zero. + //This is used so that duration can be refreshed or increased before the modifier expires to prolong it + +//Check interval is a time, in deciseconds, between validity checks. a >0 value is required here, + //the default is 300 (every 30 seconds), + //Check interval is used to generate a world time at which the next check will run + + +Please note that automated validity checking is primarily as a safety, to ensure things aren't left +when they shouldn't be. If you desire something removed in a timely manner, it's recommended to manually +call the effect's end proc from your code when you're done with it. For example, if a piece of equipment +applying a modifier is taken off. + +Setting the check interval very low just to cause the effect to be removed quickly is bad practise +it should be avoided in favour of manual removal where possible +*/ + + + + + + +//Modifier types +//These are needed globally and cannot be undefined + +#define MODIFIER_EQUIPMENT 1 +//The status effect remains valid as long as it is worn upon the affected mob. +//Worn here means it must be held in a valid equip slot, which does not include pockets, storage, or held in hands. +//The affected atom must be a mob + +#define MODIFIER_ITEM 2 +//The modifier remains valid as long as the item is in the target's contents, +//no matter how many layers deep, if it can be found by recursing up, it is valid +//This is essentially a more permissable version of equipment, and works when held, in backpacks, pockets, etc +//It can also be used on non-mob targets + +#define MODIFIER_REAGENT 3 +//The status effect remains valid as long as the dose of this chemical in a mob's reagents is above +//a specified dose value (specified in source data). +//The default of zero will keep it valid if the chemical is in them at all +//This checks for the reagent by type, in any of a mob's reagent holders - touching, blood, ingested +//Affected atom must be a mob + +#define MODIFIER_AURA 4 +//The modifier remains valid as long as the target's turf is within a range of the source's turf +//The range is defined in source data +//A range of zero is still valid if source and target are on the same turf. Sub-zero range is invalid +//Works on any affected atom + +#define MODIFIER_TIMED 5 +//The modifier remains valid as long as the duration has not expired. +//Note that a duration can be used on any time, this type is just one that does not +//check anything else but duration. +//Does not require or use a source atom +//Duration is mandatory for this type. +//Works on any atom + + +#define MODIFIER_CUSTOM 6 +//The validity check will always return 1. The author is expected to override +//it with custom validity checking behaviour. +//Does not require or use a source atom +//Does not support duration + + + +//Override Modes: +//An override parameter is passed in with New, which determines what to do if a modifier of +//the same type already exists on the target + +#define MODIFIER_OVERRIDE_DENY 0 +//The default. If a modifier of our type already exists, the new one is discarded. It will Qdel itself +//Without adding itself to any lists + +#define MODIFIER_OVERRIDE_NEIGHBOR 1 +//The new modifier ignores the existing one, and adds itself to the list alongside it +//This is not recommended but you may have a specific application +//Using the strength var and updating the effects is preferred if you want to stack multiples +//of the same type of modifier on one mob + +#define MODIFIER_OVERRIDE_REPLACE 2 +//Probably the most common nondefault and most useful. If an old modifier of the same type exists, +//Then the old one is first stopped without suspending, and deleted. +//Then the new one will add itself as normal + +#define MODIFIER_OVERRIDE_REFRESH 3 +//This mode will overwrite the variables of the old one with our new values +//It will also force it to remove and reapply its effects +//This is useful for applying a lingering modifier, by refreshing its duration + +#define MODIFIER_OVERRIDE_STRENGTHEN 4 +//Almost identical to refresh, but it will only apply if the new modifer has a higher strength value +//If the existing modifier's strength is higher than the new one, the new is discarded + +#define MODIFIER_OVERRIDE_CUSTOM 5 +//Calls a custom override function to be overwritten + + + +//This is the main proc you should call to create a modifier on a target object +/datum/proc/add_modifier(var/typepath, var/_modifier_type, var/_source = null, var/_source_data = 0, var/_strength = 0, var/_duration = 0, var/_check_interval = 0, var/override = 0) + var/datum/modifier/D = new typepath(src, _modifier_type, _source, _source_data, _strength, _duration, _check_interval) + if (D && !D.gcDestroyed) + return D.handle_registration(override) + else + return null//The modifier must have failed creation and deleted itself + + +/datum/modifier +//Config + var/check_interval = 300//How often, in deciseconds, we will recheck the validity + var/atom/target = null + var/atom/source = null + var/modifier_type = 0 + var/source_data = 0 + var/strength = 0 + var/duration = null + + //A list of equip slots which are considered 'worn'. + //For equipment modifier type to be valid, the source object must be in a mob's contents + //and equipped to one of these whitelisted slots + //This list can be overridden if you want a custom slot whitelist + var/list/valid_equipment_slots = list(slot_back, slot_wear_mask, slot_handcuffed, slot_belt, \ + slot_wear_id, slot_l_ear, slot_glasses, slot_gloves, slot_head, slot_shoes, slot_wear_suit, \ + slot_w_uniform,slot_legcuffed, slot_r_ear, slot_legs, slot_tie) + + + +//Operating Vars + var/active = 0//Whether or not the effects are currently applied + var/next_check = 0 + var/last_tick = 0 + + +//If creation of a modifier is successful, it will return a reference to itself +//If creation fails for any reason, it will return null as well as giving some debug output +/datum/modifier/New(var/atom/_target, var/_modifier_type, var/_source = null, var/_source_data = 0, var/_strength = 0, var/_duration = 0, var/_check_interval = 0) + ..() + target = _target + modifier_type = _modifier_type + source = _source + source_data = _source_data + strength = _strength + last_tick = world.time + if (_duration) + duration = _duration + + if (_check_interval) + check_interval = _check_interval + + if (!target || !modifier_type) + return invalid_creation("No target and/or no modifier type was submitted") + + switch (modifier_type) + if (MODIFIER_EQUIPMENT) + if (!istype(target, /mob)) + return invalid_creation("Equipment type requires a mob target") + + if (!source || !istype(source, /obj)) + return invalid_creation("Equipment type requires an object source") + + //TODO: Port equip slot var + if (MODIFIER_ITEM) + if (!source || !istype(source, /obj)) + return invalid_creation("Item type requires a source") + + if (MODIFIER_REAGENT) + if (!istype(target, /mob) || !istype(source, /datum/reagent)) + return invalid_creation("Reagent type requires a mob target and a reagent source") + + if (MODIFIER_AURA) + if (!source || !istype(source, /atom)) + return invalid_creation("Aura type requires an atom source") + + if (MODIFIER_TIMED) + if (!duration || duration <= 0) + return invalid_creation("Timed type requires a duration") + if (MODIFIER_CUSTOM) + //No code here, just to prevent else + else + return invalid_creation("Invalid or unrecognised modifier type")//Not a valid modifier type. + return 1 + + +/datum/modifier/proc/handle_registration(var/override = 0) + var/datum/modifier/existing = null + for (var/datum/modifier/D in target.modifiers) + if (D.type == type) + existing = D + if (!existing) + processing_modifiers += src + target.modifiers += src + activate() + return src + else + return handle_override(override, existing) + +/datum/modifier/proc/activate() + if (!gcDestroyed && !active && target) + active = 1 + return 1 + return 0 + +/datum/modifier/proc/deactivate() + active = 0 + return 1 + +/datum/modifier/proc/process() + + if (!active) + last_tick = world.time + return 0 + + if (!isnull(duration))duration -= world.time - last_tick + if (world.time > next_check) + last_tick = world.time + return check_validity() + last_tick = world.time + return 1 + +/datum/modifier/proc/check_validity() + next_check = world.time + check_interval + if (!target || target.gcDestroyed) + return validity_fail("Target is gone!") + + if (modifier_type == MODIFIER_CUSTOM) + if (custom_validity()) + return 1 + else + return validity_fail("Custom failed") + + if (!isnull(duration) && duration <= 0) + return validity_fail("Duration expired") + + else if (modifier_type == MODIFIER_TIMED) + return 1 + + if (!source || source.gcDestroyed)//If we're not timed or custom, then we need a source. If our source is gone, we are invalid + return validity_fail("Source is gone and we need one") + + switch (modifier_type) + if (MODIFIER_EQUIPMENT) + if (source.loc != target) + return validity_fail("Not in contents of mob") + + var/obj/item/I = source + if (!I.equip_slot || !(I.equip_slot in valid_equipment_slots)) + return validity_fail("Not equipped in the correct place") + + //TODO: Port equip slot var. this cant be done properly without it. This is a temporary implementation + if (MODIFIER_ITEM) + if (!source.find_up_hierarchy(target))//If source is somewhere inside target, this will be true + return validity_fail("Not found in parent hierarchy") + if (MODIFIER_REAGENT) + var/totaldose = 0 + if (!istype(source, /datum/reagent))//this shouldnt happen + return validity_fail("Source is not a reagent!") + + var/ourtype = source.type + + for (var/datum/reagent/R in target.reagents.reagent_list) + if (istype(R, ourtype)) + totaldose += R.dose + + if (istype(target, /mob/living)) + var/mob/living/L = target + + for (var/datum/reagent/R in L.ingested.reagent_list) + if (istype(R, ourtype)) + totaldose += R.dose + + if (istype(target, /mob/living/carbon)) + var/mob/living/carbon/C = target + + for (var/datum/reagent/R in C.bloodstr.reagent_list) + if (istype(R, ourtype)) + totaldose += R.dose + + for (var/datum/reagent/R in C.touching.reagent_list) + if (istype(R, ourtype)) + totaldose += R.dose + + if (totaldose < source_data) + return validity_fail("Dose is too low!") + + if (MODIFIER_AURA) + if (!(get_turf(target) in range(source_data, get_turf(source)))) + return validity_fail("Target not in range of source") + + return 1 + + +//Override this without a call to parent, for custom validity conditions +/datum/modifier/proc/custom_validity() + return 1 + +/datum/modifier/proc/validity_fail(var/reason) + //world << "MODIFIER VALIDITY FAIL: [reason]" + qdel(src) + return 0 + +/datum/modifier/proc/invalid_creation(var/reason) + log_debug("ERROR: [src] MODIFIER CREATION FAILED on [target]: [reason]") + qdel(src) + return 0 + +//called by any object to either pause or remove the proc. +/datum/modifier/proc/stop(var/instant = 0, var/suspend = 0) + + //Instant var removes us from the lists immediately, instead of waiting til next frame when qdel goes through + if (instant) + if (target) + target.modifiers -= src + processing_modifiers -= src + + if (suspend) + deactivate() + else + qdel(src) + +//Suspends and immediately restarts the proc, thus reapplying its effects +/datum/modifier/proc/refresh() + deactivate() + activate() + + +/datum/modifier/Destroy() + if (active) + deactivate() + if (target) + target.modifiers -= src + processing_modifiers -= src + ..() + + +//Handles overriding an existing modifier of the same type. +//This function should return either src or the existing, depending on whether or not src will be kept +/datum/modifier/proc/handle_override(var/override, var/datum/modifier/existing) + switch(override) + if (MODIFIER_OVERRIDE_DENY) + qdel(src) + return existing + if (MODIFIER_OVERRIDE_NEIGHBOR) + processing_modifiers += src + target.modifiers += src + return src + if (MODIFIER_OVERRIDE_REPLACE) + existing.stop() + processing_modifiers += src + target.modifiers += src + activate() + return src + if (MODIFIER_OVERRIDE_REFRESH) + existing.strength = strength + existing.duration = duration + existing.source = source + existing.source_data = source_data + if (existing.check_validity()) + existing.refresh() + qdel(src) + return existing + else + qdel(src) + return null//this should only happen if you overwrote the existing with bad values. + //It will result in both existing and src being deleted + //The null return will allow the source to see this went wrong and remake the modifier + if (MODIFIER_OVERRIDE_STRENGTHEN) + if (strength > existing.strength) + existing.strength = strength + existing.duration = duration + existing.source = source + existing.source_data = source_data + if (existing.check_validity()) + existing.refresh() + qdel(src) + return existing + qdel(src) + return null + qdel(src) + return existing + + if (MODIFIER_OVERRIDE_CUSTOM) + return custom_override(existing) + else + qdel(src) + return existing + +//This function should be completely overwritten, without a call to parent, to specify custom override +/datum/modifier/proc/custom_override(var/datum/modifier/existing) + qdel(src) + return existing diff --git a/code/game/modifiers/modifiers_chem.dm b/code/game/modifiers/modifiers_chem.dm new file mode 100644 index 00000000000..c99c09dba9a --- /dev/null +++ b/code/game/modifiers/modifiers_chem.dm @@ -0,0 +1,73 @@ +//Stimulant modifier. Applied in varying strengths by hyperzine and caffienated drinks +//Increases sprinting speed, walk speed, and stamina regen +/datum/modifier/stimulant + var/sprint_speed_added = 0 + var/regen_added = 0 + var/delay_added = 0 + +/datum/modifier/stimulant/activate() + ..() + if (isliving(target)) + var/mob/living/L = target + + sprint_speed_added = 0.2 * strength + L.sprint_speed_factor += sprint_speed_added + + regen_added = L.stamina_recovery * 0.3 * strength + L.stamina_recovery += regen_added + + delay_added = -1.5 * strength + L.move_delay_mod += delay_added + + + +/datum/modifier/stimulant/deactivate() + ..() + if (isliving(target)) + var/mob/living/L = target + L.sprint_speed_factor -= sprint_speed_added + L.stamina_recovery -= regen_added + L.move_delay_mod -= delay_added + + + +//Adrenaline, granted by synaptizine and inaprovaline, with different strengths for each +//Allows the body to endure more, increasing speed a little, stamina a lot, stamina regen a lot, +//and reducing sprint costs +//Synaptizine applies it at strength 1, inaprovaline applies it at strength 0.6 +//Is applied using strengthen override mode, so synaptizine will replace inaprovaline if both are present +/datum/modifier/adrenaline + var/speed_added = 0 + var/stamina_added = 0 + var/cost_added = 0 + var/regen_added = 0 + +/datum/modifier/adrenaline/activate() + ..() + if (isliving(target)) + var/mob/living/L = target + speed_added += 0.1*strength + L.sprint_speed_factor += speed_added + + stamina_added = L.max_stamina * strength + L.max_stamina += stamina_added + + cost_added = -0.35 * strength + L.sprint_cost_factor += cost_added + + regen_added = max ((L.stamina_recovery * 0.7 * strength), 5) + L.stamina_recovery += regen_added + +/datum/modifier/adrenaline/deactivate() + ..() + if (isliving(target)) + var/mob/living/L = target + + L.stamina_recovery -= regen_added + L.max_stamina -= stamina_added + L.sprint_cost_factor -= cost_added + L.sprint_speed_factor -= speed_added + + + + diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 7a6579d9761..1b44bc8130c 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -249,6 +249,7 @@ // note this isn't called during the initial dressing of a player /obj/item/proc/equipped(var/mob/user, var/slot) layer = 20 + equip_slot = slot if(user.client) user.client.screen |= src if(user.pulling == src) user.stop_pulling() return @@ -656,3 +657,47 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out. /obj/item/proc/pwr_drain() return 0 // Process Kill + + +//a proc that any worn thing can call to update its itemstate +//Should be cheaper than calling regenerate icons on the mob +/obj/item/proc/update_worn_icon() + if (!equip_slot || !istype(loc, /mob)) + return + + var/mob/M = loc + switch (equip_slot) + if (slot_back) + M.update_inv_back() + if (slot_wear_mask) + M.update_inv_wear_mask() + if (slot_l_hand) + M.update_inv_l_hand() + if (slot_r_hand) + M.update_inv_r_hand() + if (slot_belt) + M.update_inv_belt() + if (slot_wear_id) + M.update_inv_wear_id() + if (slot_l_ear) + M.update_inv_ears() + if (slot_r_ear) + M.update_inv_ears() + if (slot_glasses) + M.update_inv_glasses() + if (slot_gloves) + M.update_inv_gloves() + if (slot_head) + M.update_inv_head() + if (slot_shoes) + M.update_inv_shoes() + if (slot_wear_suit) + M.update_inv_wear_suit() + if (slot_w_uniform) + M.update_inv_w_uniform() + if (slot_l_store) + M.update_inv_pockets() + if (slot_r_store) + M.update_inv_pockets() + if (slot_s_store) + M.update_inv_s_store() diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index 76729420e0e..0f263dad9f4 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -25,6 +25,7 @@ var/list/icon_supported_species_tags //Used with icon_auto_adapt, a list of species which have differing appearances for this item var/icon_species_in_hand = 0//If 1, we will use the species tag even for rendering this item in the left/right hand. + var/equip_slot = 0 /obj/Destroy() processing_objects -= src nanomanager.close_uis(src) diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 2fa4d08ce5e..66eac7b449c 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -145,14 +145,15 @@ if(istype(MOB) && !MOB.lying && footstep_sound) if(istype(MOB.shoes, /obj/item/clothing/shoes) && !MOB.shoes:silent) if(MOB.m_intent == "run") + playsound(MOB, footstep_sound, 70, 1) + else //Run and walk footsteps switched, because walk is the normal movement mode now if(MOB.footstep >= 2) MOB.footstep = 0 else MOB.footstep++ if(MOB.footstep == 0) - playsound(MOB, footstep_sound, 50, 1) // this will get annoying very fast. - Tell them to mute it then -_- - else - playsound(MOB, footstep_sound, 40, 1) + playsound(MOB, footstep_sound, 40, 1) + else if(!istype(src, /turf/space)) diff --git a/code/global.dm b/code/global.dm index 4958d987038..3294807b76c 100644 --- a/code/global.dm +++ b/code/global.dm @@ -8,6 +8,7 @@ var/global/obj/effect/datacore/data_core = null var/global/list/all_areas = list() var/global/list/machines = list() var/global/list/processing_objects = list() +var/global/list/processing_modifiers = list() var/global/list/processing_power_items = list() var/global/list/active_diseases = list() var/global/list/med_hud_users = list() // List of all entities using a medical HUD. diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 3049f3ae15f..3b34cec9022 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -1176,6 +1176,12 @@ if (src.is_diona()) setup_gestalt(1) + max_stamina = species.stamina + stamina = max_stamina + sprint_speed_factor = species.sprint_speed_factor + sprint_cost_factor = species.sprint_cost_factor + stamina_recovery = species.stamina_recovery + exhaust_threshold = species.exhaust_threshold if(species) return 1 else diff --git a/code/modules/mob/living/carbon/human/human_damage.dm b/code/modules/mob/living/carbon/human/human_damage.dm index 89107fbea28..9eb87c904a5 100644 --- a/code/modules/mob/living/carbon/human/human_damage.dm +++ b/code/modules/mob/living/carbon/human/human_damage.dm @@ -16,7 +16,7 @@ var/clone_l = getCloneLoss() health = maxHealth - oxy_l - tox_l - clone_l - total_burn - total_brute - + update_health_display() //TODO: fix husking if( ((maxHealth - total_burn) < config.health_threshold_dead) && stat == DEAD) ChangeToHusk() diff --git a/code/modules/mob/living/carbon/human/human_movement.dm b/code/modules/mob/living/carbon/human/human_movement.dm index d660d766a30..a88a08b57b8 100644 --- a/code/modules/mob/living/carbon/human/human_movement.dm +++ b/code/modules/mob/living/carbon/human/human_movement.dm @@ -1,7 +1,6 @@ /mob/living/carbon/human/movement_delay() var/tally = 0 - if(species.slowdown) tally = species.slowdown @@ -10,8 +9,7 @@ if(embedded_flag) handle_embedded_objects() //Moving with objects stuck in you can cause bad times. - if(CE_SPEEDBOOST in chem_effects) - return -1 + var/health_deficiency = (100 - health) if(health_deficiency >= 40) tally += (health_deficiency / 25) @@ -49,18 +47,27 @@ if(shock_stage >= 10) tally += 3 + if (drowsyness) tally += 6 + if(FAT in src.mutations) tally += 1.5 if (bodytemperature < 283.222) tally += (283.222 - bodytemperature) / 10 * 1.75 tally += max(2 * stance_damage, 0) //damaged/missing feet or legs is slow - if(mRun in mutations) tally = 0 + tally += move_delay_mod + + if(tally > 0 && (CE_SPEEDBOOST in chem_effects)) + tally = max(0, tally-3) + return (tally+config.human_delay) + + + /mob/living/carbon/human/Process_Spacemove(var/check_drift = 0) //Can we act? if(restrained()) return 0 diff --git a/code/modules/mob/living/carbon/human/intoxication.dm b/code/modules/mob/living/carbon/human/intoxication.dm index 524e9f85f5e..24fae1d543d 100644 --- a/code/modules/mob/living/carbon/human/intoxication.dm +++ b/code/modules/mob/living/carbon/human/intoxication.dm @@ -1,7 +1,9 @@ #define AE_DIZZY 5 +#define AE_BUZZED_MIN 6 #define AE_SLURRING 15 #define AE_CONFUSION 18 #define AE_CLUMSY 22 +#define AE_BUZZED_MAX 24 #define AE_BLURRING 25 #define AE_VOMIT 40 #define AE_DROWSY 55 @@ -70,3 +72,48 @@ var/mob/living/carbon/human/alcohol_clumsy = 0 if(intoxication > AE_BLACKOUT*SR) // Pass out paralysis = max(paralysis, 20) sleeping = max(sleeping, 30) + + if( intoxication >= AE_BUZZED_MIN && intoxication <= AE_BUZZED_MAX && !(locate(/datum/modifier/buzzed) in modifiers)) + add_modifier(/datum/modifier/buzzed, MODIFIER_CUSTOM) + + +//Pleasant feeling from being slightly drunk +//Makes you faster and reduces sprint cost +//Wears off if you get too drunk or too sober, a balance must be maintained +/datum/modifier/buzzed + +/datum/modifier/buzzed/activate() + ..() + if (ishuman(target)) + var/mob/living/carbon/human/H = target + H.move_delay_mod += -0.75 + + H.sprint_cost_factor += -0.1 + +/datum/modifier/buzzed/deactivate() + ..() + if (ishuman(target)) + var/mob/living/carbon/human/H = target + H.move_delay_mod -= -0.75 + + H.sprint_cost_factor -= -0.1 + +/datum/modifier/buzzed/custom_validity() + if (ishuman(target)) + var/mob/living/carbon/human/H = target + if (H.intoxication >= AE_BUZZED_MIN && H.intoxication <= AE_BUZZED_MAX) + + return 1 + return 0 + +#undef AE_DIZZY +#undef AE_BUZZED_MIN +#undef AE_SLURRING +#undef AE_CONFUSION +#undef AE_CLUMSY +#undef AE_BUZZED_MAX +#undef AE_BLURRING +#undef AE_VOMIT +#undef AE_DROWSY +#undef AE_OVERDOSE +#undef AE_BLACKOUT \ No newline at end of file diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index c946099f10a..958bbd9b3c7 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -102,6 +102,9 @@ handle_heartbeat() + //Handles regenerating stamina if we have sufficient air and no oxyloss + handle_stamina() + if (is_diona()) diona_handle_light(DS) @@ -559,7 +562,9 @@ failed_last_breath = 1 else failed_last_breath = 0 - adjustOxyLoss(-5) + adjustOxyLoss(-3) + + // Hot air hurts :( @@ -964,7 +969,7 @@ return 1 //UNCONSCIOUS. NO-ONE IS HOME - if((getOxyLoss() > 50) || (health <= config.health_threshold_crit)) + if((getOxyLoss() > exhaust_threshold) || (health <= config.health_threshold_crit)) Paralyse(3) if(hallucination) @@ -1179,24 +1184,7 @@ damageoverlay.overlays += I else //Oxygen damage overlay - if(oxyloss) - var/image/I - switch(oxyloss) - if(10 to 20) - I = overlays_cache[11] - if(20 to 25) - I = overlays_cache[12] - if(25 to 30) - I = overlays_cache[13] - if(30 to 35) - I = overlays_cache[14] - if(35 to 40) - I = overlays_cache[15] - if(40 to 45) - I = overlays_cache[16] - if(45 to INFINITY) - I = overlays_cache[17] - damageoverlay.overlays += I + update_oxy_overlay() // Vampire frenzy overlay. if (mind.vampire) @@ -1277,23 +1265,9 @@ if(equipped_glasses) process_glasses(equipped_glasses) - if(healths) - if (analgesic > 100) - healths.icon_state = "health_numb" - else - switch(hal_screwyhud) - if(1) healths.icon_state = "health6" - if(2) healths.icon_state = "health7" - else - //switch(health - halloss) - switch(health - traumatic_shock) - if(100 to INFINITY) healths.icon_state = "health0" - if(80 to 100) healths.icon_state = "health1" - if(60 to 80) healths.icon_state = "health2" - if(40 to 60) healths.icon_state = "health3" - if(20 to 40) healths.icon_state = "health4" - if(0 to 20) healths.icon_state = "health5" - else healths.icon_state = "health6" + + update_health_display() + if(nutrition_icon) switch(nutrition) @@ -1748,5 +1722,62 @@ restore_blood() ..() +/mob/living/carbon/human/proc/handle_stamina() + if (species.stamina == -1)//If species stamina is -1, it has special mechanics which will be handled elsewhere + return//so quit this function + + if (failed_last_breath || oxyloss > exhaust_threshold)//Can't catch our breath if we're suffocating + return + + if (stamina != max_stamina) + //Any suffocation damage slows stamina regen. + //This includes oxyloss from low blood levels + var/regen = stamina_recovery * (1 - min(((oxyloss*2) / exhaust_threshold), 1)) + if (regen > 0) + stamina = min(max_stamina, stamina+regen) + nutrition -= stamina_recovery*0.4 + hud_used.move_intent.update_move_icon(src) + +/mob/living/carbon/human/proc/update_health_display() + if(!healths) + return + + if (analgesic > 100) + healths.icon_state = "health_numb" + else + switch(hal_screwyhud) + if(1) healths.icon_state = "health6" + if(2) healths.icon_state = "health7" + else + //switch(health - halloss) + switch(health - traumatic_shock) + if(100 to INFINITY) healths.icon_state = "health0" + if(80 to 100) healths.icon_state = "health1" + if(60 to 80) healths.icon_state = "health2" + if(40 to 60) healths.icon_state = "health3" + if(20 to 40) healths.icon_state = "health4" + if(0 to 20) healths.icon_state = "health5" + else healths.icon_state = "health6" + +/mob/living/carbon/human/proc/update_oxy_overlay() + if(oxyloss) + var/image/I + switch(oxyloss) + if(10 to 20) + I = overlays_cache[11] + if(20 to 25) + I = overlays_cache[12] + if(25 to 30) + I = overlays_cache[13] + if(30 to 35) + I = overlays_cache[14] + if(35 to 40) + I = overlays_cache[15] + if(40 to 45) + I = overlays_cache[16] + if(45 to INFINITY) + I = overlays_cache[17] + damageoverlay.overlays += I + #undef HUMAN_MAX_OXYLOSS #undef HUMAN_CRIT_MAX_OXYLOSS diff --git a/code/modules/mob/living/carbon/human/species/outsider/skeleton.dm b/code/modules/mob/living/carbon/human/species/outsider/skeleton.dm index abbcdf5e3bc..2b1a97405dc 100644 --- a/code/modules/mob/living/carbon/human/species/outsider/skeleton.dm +++ b/code/modules/mob/living/carbon/human/species/outsider/skeleton.dm @@ -54,3 +54,8 @@ "l_foot" = list("path" = /obj/item/organ/external/foot/skeleton), "r_foot" = list("path" = /obj/item/organ/external/foot/right/skeleton) ) + + stamina = 500 //Tireless automatons + stamina_recovery = 1 + sprint_speed_factor = 0.3 + exhaust_threshold = 0 //No oxyloss, so zero threshold diff --git a/code/modules/mob/living/carbon/human/species/outsider/vox.dm b/code/modules/mob/living/carbon/human/species/outsider/vox.dm index 3a0892b9d4d..583f24fb480 100644 --- a/code/modules/mob/living/carbon/human/species/outsider/vox.dm +++ b/code/modules/mob/living/carbon/human/species/outsider/vox.dm @@ -15,6 +15,12 @@ smell.

Most humans will never meet a Vox raider, instead learning of this insular species through \ dealing with their traders and merchants; those that do rarely enjoy the experience." + + stamina = 120 // Vox are even faster than unathi and can go longer, but recover slowly + sprint_speed_factor = 3 + stamina_recovery = 1 + sprint_cost_factor = 1.7 + speech_sounds = list('sound/voice/shriek1.ogg') speech_chance = 20 diff --git a/code/modules/mob/living/carbon/human/species/species.dm b/code/modules/mob/living/carbon/human/species/species.dm index 5404a313aa3..ecbf194a7f1 100644 --- a/code/modules/mob/living/carbon/human/species/species.dm +++ b/code/modules/mob/living/carbon/human/species/species.dm @@ -99,18 +99,24 @@ var/hud_type // Body/form vars. - var/list/inherent_verbs // Species-specific verbs. - var/has_fine_manipulation = 1 // Can use small items. - var/siemens_coefficient = 1 // The lower, the thicker the skin and better the insulation. - var/darksight = 2 // Native darksight distance. - var/flags = 0 // Various specific features. - var/slowdown = 0 // Passive movement speed malus (or boost, if negative) - var/primitive_form // Lesser form, if any (ie. monkey for humans) - var/greater_form // Greater form, if any, ie. human for monkeys. + var/list/inherent_verbs // Species-specific verbs. + var/has_fine_manipulation = 1 // Can use small items. + var/siemens_coefficient = 1 // The lower, the thicker the skin and better the insulation. + var/darksight = 2 // Native darksight distance. + var/flags = 0 // Various specific features. + var/slowdown = 0 // Passive movement speed malus (or boost, if negative) + var/primitive_form // Lesser form, if any (ie. monkey for humans) + var/greater_form // Greater form, if any, ie. human for monkeys. var/holder_type - var/gluttonous // Can eat some mobs. 1 for mice, 2 for monkeys, 3 for people. - var/rarity_value = 1 // Relative rarity/collector value for this species. - var/ethanol_resistance = 1 // How well the mob resists alcohol, lower values get drunk faster, higher values need to drink more + var/gluttonous // Can eat some mobs. 1 for mice, 2 for monkeys, 3 for people. + var/rarity_value = 1 // Relative rarity/collector value for this species. + var/ethanol_resistance = 1 // How well the mob resists alcohol, lower values get drunk faster, higher values need to drink more + + var/stamina = 100 // The maximum stamina this species has. Determines how long it can sprint + var/stamina_recovery = 3 // Flat amount of stamina species recovers per proc + var/sprint_speed_factor = 0.65 // The percentage of bonus speed you get when sprinting. 0.4 = 40% + var/sprint_cost_factor = 1.0 // Multiplier on stamina cost for sprinting + var/exhaust_threshold = 50 // When stamina runs out, the mob takes oxyloss up til this value. Then collapses and drops to walk // Determines the organs that the species spawns with and var/list/has_organ = list( // which required-organ checks are conducted. @@ -310,3 +316,35 @@ // Called in life() when the mob has no client. /datum/species/proc/handle_npc(var/mob/living/carbon/human/H) return + +/datum/species/proc/handle_sprint_cost(var/mob/living/carbon/human/H, var/cost) + cost *= H.sprint_cost_factor + if (H.stamina == -1) + log_debug("Error: Species with special sprint mechanics has not overridden cost function.") + return 0 + + var/remainder = 0 + if (H.stamina > cost) + H.stamina -= cost + H.hud_used.move_intent.update_move_icon(H) + return 1 + else if (H.stamina > 0) + remainder = cost - H.stamina + H.stamina = 0 + else + remainder = cost + + H.adjustOxyLoss(remainder*0.4) + H.adjustHalLoss(remainder*0.3) + H.updatehealth() + H.update_oxy_overlay() + + if (H.oxyloss >= exhaust_threshold) + H.Weaken(10) + H.m_intent = "walk" + H.hud_used.move_intent.update_move_icon(H) + H << span("danger", "You're too exhausted to run anymore! You collapse in a heap on the floor.") + return 0 + H.hud_used.move_intent.update_move_icon(H) + return 1 + diff --git a/code/modules/mob/living/carbon/human/species/station/golem.dm b/code/modules/mob/living/carbon/human/species/station/golem.dm index f9c92455d3a..9b4387be017 100644 --- a/code/modules/mob/living/carbon/human/species/station/golem.dm +++ b/code/modules/mob/living/carbon/human/species/station/golem.dm @@ -33,6 +33,11 @@ death_message = "becomes completely motionless..." + stamina = 500 //Tireless automatons + stamina_recovery = 1 + sprint_speed_factor = 0.3 + exhaust_threshold = 0 //No oxyloss, so zero threshold + /datum/species/golem/handle_post_spawn(var/mob/living/carbon/human/H) if(H.mind) H.mind.assigned_role = "Golem" diff --git a/code/modules/mob/living/carbon/human/species/station/station.dm b/code/modules/mob/living/carbon/human/species/station/station.dm index 89721a7f6a9..a7cd36f21ee 100644 --- a/code/modules/mob/living/carbon/human/species/station/station.dm +++ b/code/modules/mob/living/carbon/human/species/station/station.dm @@ -13,6 +13,11 @@ flags = CAN_JOIN | HAS_SKIN_TONE | HAS_LIPS | HAS_UNDERWEAR | HAS_EYE_COLOR + stamina = 130 // Humans can sprint for longer than any other species + stamina_recovery = 5 + sprint_speed_factor = 0.8 + sprint_cost_factor = 0.8 + /datum/species/unathi name = "Unathi" short_name = "una" @@ -27,6 +32,10 @@ darksight = 3 gluttonous = 1 ethanol_resistance = 0.4 + stamina = 120 // Unathi have the shortest but fastest sprint of all + sprint_speed_factor = 3 + stamina_recovery = 5 + sprint_cost_factor = 2 rarity_value = 3 blurb = "A heavily reptillian species, Unathi (or 'Sinta as they call themselves) hail from the \ @@ -90,6 +99,12 @@ ethanol_resistance = 0.8//Gets drunk a little faster rarity_value = 2 + stamina = 90 // Tajarans evolved to maintain a steady pace in the snow, sprinting wastes energy + stamina_recovery = 4 + sprint_speed_factor = 0.55 + sprint_cost_factor = 1.1 + + blurb = "The Tajaran race is a species of feline-like bipeds hailing from the planet of Ahdomai in the \ S'randarr system. They have been brought up into the space age by the Humans and Skrell, and have been \ influenced heavily by their long history of Slavemaster rule. They have a structured, clan-influenced way \ @@ -150,6 +165,10 @@ reagent_tag = IS_SKRELL ethanol_resistance = 0.5//gets drunk faster + stamina = 90 + sprint_speed_factor = 1.2 //Evolved for rapid escapes from predators + + /datum/species/diona name = "Diona" short_name = "dio" @@ -222,6 +241,41 @@ reagent_tag = IS_DIONA + stamina = -1 // Diona sprinting uses energy instead of stamina + sprint_speed_factor = 0.4 //Speed gained is minor + + +/datum/species/diona/handle_sprint_cost(var/mob/living/carbon/human/H, var/cost) + var/datum/dionastats/DS = H.get_dionastats() + + if (!DS) + return 0 //Something is very wrong + + var/remainder = cost + + if (H.radiation) + if (H.radiation > (cost*0.5))//Radiation counts as double energy + H.radiation -= cost*0.5 + return 1 + else + remainder = cost - (H.radiation*2) + H.radiation = 0 + + if (DS.stored_energy > remainder) + DS.stored_energy -= remainder + return 1 + else + remainder -= DS.stored_energy + DS.stored_energy = 0 + H.adjustHalLoss(remainder*5, 1) + H.updatehealth() + H.m_intent = "walk" + H.hud_used.move_intent.update_move_icon(H) + H << span("danger", "We have expended our energy reserves, and cannot continue to move at such a pace. We must find light!") + return 0 + + + /datum/species/diona/can_understand(var/mob/other) var/mob/living/carbon/alien/diona/D = other if(istype(D)) @@ -283,6 +337,26 @@ has_organ = list() //TODO: Positronic brain. + stamina = -1 // Machines use power and generate heat, stamina is not a thing + sprint_speed_factor = 0.8 // About as capable of speed as a human + + +/datum/species/machine/handle_sprint_cost(var/mob/living/carbon/human/H, var/cost) + if (H.stat == CONSCIOUS) + H.bodytemperature += cost*1.5 + H.nutrition -= cost + if (H.nutrition > 0) + return 1 + else + H.Weaken(30) + H.m_intent = "walk" + H.hud_used.move_intent.update_move_icon(H) + H << span("danger", "ERROR: Power reserves depleted, emergency shutdown engaged. Backup power will come online in 60 seconds, initiate charging as primary directive.") + playsound(H.loc, 'sound/machines/buzz-two.ogg', 100, 0) + return 0 + + + /datum/species/machine/equip_survival_gear(var/mob/living/carbon/human/H) /datum/species/machine/handle_death(var/mob/living/carbon/human/H) @@ -332,6 +406,12 @@ flesh_color = "#E6E600" base_color = "#575757" + stamina = 100 // Long period of sprinting, but relatively low speed gain + sprint_speed_factor = 0.5 + sprint_cost_factor = 0.3 + stamina_recovery = 1//slow recovery + + inherent_verbs = list( /mob/living/carbon/human/proc/bugbite, //weaker version of gut. ) diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index edd441b07c4..f781b57f227 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -47,4 +47,16 @@ var/eat_types = 0//This is a bitfield which must be initialised in New(). The valid values for it are in devour.dm var/datum/reagents/metabolism/ingested = null var/underdoor //Used for mobs that can walk through maintenance hatches - drones, pais, and spiderbots - var/life_tick = 0 // The amount of life ticks that have processed on this mob. \ No newline at end of file + var/life_tick = 0 // The amount of life ticks that have processed on this mob. + + + //These values are duplicated from the species datum so we can handle things on a per-mob basis, allows for chemicals to affect them + var/stamina = 0 + var/max_stamina = 100//Maximum stamina. We start taking oxyloss when this runs out while sprinting + var/sprint_speed_factor = 0.4 + var/sprint_cost_factor = 1 + var/stamina_recovery = 1 + var/min_walk_delay = 0//When move intent is walk, movedelay is clamped to this value as a lower bound + var/exhaust_threshold = 50 + + var/move_delay_mod = 0//Added to move delay, used for calculating movement speeds. Provides a centralised value for modifiers to alter diff --git a/code/modules/mob/living/living_powers.dm b/code/modules/mob/living/living_powers.dm index b7d640a5ee8..1eb7c868f93 100644 --- a/code/modules/mob/living/living_powers.dm +++ b/code/modules/mob/living/living_powers.dm @@ -32,10 +32,37 @@ attempt_devour(L, eat_types, mouth_size) -/* -/mob/living/verb/devourverb(var/mob/living/victim)//For situations where species inherent verbs isnt suitable - set category = "Abilities" - set name = "Devour Creature" - set desc = "Attempt to eat a nearby creature, swallowing it whole if small enough, or eating it piece by piece otherwise" - attempt_devour(victim, eat_types, mouth_size) -*/ \ No newline at end of file +/mob/living/verb/set_walk_speed() + set category = "IC" + set name = "Adjust walk speed" + set desc = "Allows you to adjust your walking speed to a slower value than normal, or reset it. Does not make you faster." + + + //First a quick block of code to calculate our normal/best movedelay + var/delay = config.walk_speed + movement_delay() + var/speed = 1 / (delay/10) + var/newspeed + var/list/options = list("No limit",speed*0.95,speed*0.9,speed*0.85,speed*0.8,speed*0.7,speed*0.6,speed*0.5, "Custom") + + + var/response = input(src, "Your current walking speed is [speed] tiles per second. This menu allows you to limit it to a lower value, by applying a multiplier to that. Please choose a value, select custom to enter your own, or No limit to set your walk speed to the maximum. This menu will not make you move any faster than usual, it is only for allowing you to move at a slower pace than normal, for roleplay purposes. Values set here will not affect your sprinting speed", "Limit Walking speed") as null|anything in options + + if (isnull(response)) + return + else if (response == "No limit") + src << "Movement speed has now been set to normal, limits removed." + src.min_walk_delay = 0 + return + else if (response == "Custom") + response = input(src, "Please enter the exact speed you want to walk at, in tiles per second. This value must be less than [speed] and greater than zero", "Limit Walking speed") as num + newspeed = response + else + newspeed = text2num(response) + + if (!newspeed || newspeed >= speed || newspeed <= 0) + src << "Error, invalid value entered. Walk speed has not been changed" + return + + + src << "Walking speed has now been limited to [newspeed] tiles per second, which is [(newspeed/speed)*100]% of your normal walking speed." + src.min_walk_delay = (10 / newspeed) \ No newline at end of file diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm index 6ce7679a752..2aff2a8a4ae 100644 --- a/code/modules/mob/mob_defines.dm +++ b/code/modules/mob/mob_defines.dm @@ -119,7 +119,7 @@ var/shakecamera = 0 var/a_intent = I_HELP//Living var/m_int = null//Living - var/m_intent = "run"//Living + var/m_intent = "walk"//Living var/lastKnownIP = null var/obj/buckled = null//Living var/obj/item/l_hand = null//Living diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index ffacc2629a7..a02f965be86 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -179,7 +179,7 @@ Process_Incorpmove(direct) return - if(moving) return 0 + if(moving) return 0 if(world.time < move_delay) return @@ -209,6 +209,8 @@ if(item.zoom) item.zoom() break + + //TODO: REMOVE This stupid zooming stuff /* if(locate(/obj/item/weapon/gun/energy/sniperrifle, mob.contents)) // If mob moves while zoomed in with sniper rifle, unzoom them. var/obj/item/weapon/gun/energy/sniperrifle/s = locate() in mob @@ -257,14 +259,21 @@ move_delay = world.time//set move delay mob.last_move_intent = world.time + 10 - switch(mob.m_intent) - if("run") - if(mob.drowsyness > 0) - move_delay += 6 - move_delay += 1+config.run_speed - if("walk") - move_delay += 7+config.walk_speed - move_delay += mob.movement_delay() + if (ishuman(mob)) + + var/mob/living/carbon/human/H = mob + var/tally = mob.movement_delay()+config.walk_speed + //If we're sprinting and able to continue sprinting, then apply the sprint bonus ontop of this + if (H.m_intent == "run" && H.species.handle_sprint_cost(H, tally))//This will return false if we collapse from exhaustion + tally = tally/(1+H.sprint_speed_factor) + else + tally = max(tally, H.min_walk_delay)//clamp walking speed if its limited + move_delay += tally + + else + move_delay += config.walk_speed + move_delay += mob.movement_delay() + var/tickcomp = 0 //moved this out here so we can use it for vehicles if(config.Tickcomp) @@ -272,10 +281,11 @@ tickcomp = ((1/(world.tick_lag))*1.3) - 1.3 move_delay = move_delay + tickcomp + if(istype(mob.buckled, /obj/vehicle)) //manually set move_delay for vehicles so we don't inherit any mob movement penalties //specific vehicle move delays are set in code\modules\vehicles\vehicle.dm - move_delay = world.time + tickcomp + move_delay = world.time //drunk driving if(mob.confused && prob(20)) direct = pick(cardinal) @@ -351,7 +361,6 @@ G.adjust_position() moving = 0 - return . return diff --git a/code/modules/reagents/Chemistry-Reagents.dm b/code/modules/reagents/Chemistry-Reagents.dm index 12a12a0fade..c57f416101f 100644 --- a/code/modules/reagents/Chemistry-Reagents.dm +++ b/code/modules/reagents/Chemistry-Reagents.dm @@ -39,6 +39,10 @@ return if(!affects_dead && M.stat == DEAD) return + + if(!dose && volume)//If dose is currently zero, we do the first effect + initial_effect(M, alien) + if(overdose && (dose > overdose) && (location != CHEM_TOUCH)) overdose(M, alien) var/removed = metabolism @@ -61,6 +65,11 @@ remove_self(removed) return + +//Initial effect is called once when the reagent first starts affecting a mob. +/datum/reagent/proc/initial_effect(var/mob/living/carbon/M, var/alien) + return + /datum/reagent/proc/affect_blood(var/mob/living/carbon/M, var/alien, var/removed) return diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Dispenser.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Dispenser.dm index 6154c963cb2..18099b62e3e 100644 --- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Dispenser.dm +++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Dispenser.dm @@ -65,6 +65,8 @@ var/adj_temp = 0 var/targ_temp = 310 var/halluci = 0 + var/datum/modifier/caffeine_mod + var/caffeine = 0 glass_icon_state = "glass_clear" glass_name = "glass of ethanol" @@ -103,6 +105,10 @@ if(halluci) M.hallucination = max(M.hallucination, halluci) + if (caffeine && !caffeine_mod) + caffeine_mod = M.add_modifier(/datum/modifier/stimulant, MODIFIER_REAGENT, src, _strength = caffeine, override = MODIFIER_OVERRIDE_STRENGTHEN) + + /datum/reagent/ethanol/touch_obj(var/obj/O) if(istype(O, /obj/item/weapon/paper)) diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Food-Drinks.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Food-Drinks.dm index 445fe5506ea..6d29808ad7b 100644 --- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Food-Drinks.dm +++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Food-Drinks.dm @@ -354,12 +354,17 @@ var/adj_drowsy = 0 var/adj_sleepy = 0 var/adj_temp = 0 + var/caffeine = 0 // strength of stimulant effect, since so many drinks use it + var/datum/modifier/modifier = null /datum/reagent/drink/affect_blood(var/mob/living/carbon/M, var/alien, var/removed) M.adjustToxLoss(removed) // Probably not a good idea; not very deadly though return /datum/reagent/drink/affect_ingest(var/mob/living/carbon/M, var/alien, var/removed) + if (caffeine && !modifier) + modifier = M.add_modifier(/datum/modifier/stimulant, MODIFIER_REAGENT, src, _strength = caffeine, override = MODIFIER_OVERRIDE_STRENGTHEN) + M.nutrition += nutrition * removed M.dizziness = max(0, M.dizziness + adj_dizzy) M.drowsyness = max(0, M.drowsyness + adj_drowsy) @@ -586,6 +591,7 @@ adj_sleepy = -2 adj_temp = 25 overdose = 45 + caffeine = 0.3 glass_icon_state = "hot_coffee" glass_name = "cup of coffee" @@ -606,6 +612,7 @@ M.bodytemperature = max(310, M.bodytemperature - (5 * TEMPERATURE_DAMAGE_COEFFICIENT)) + /datum/reagent/drink/coffee/overdose(var/mob/living/carbon/M, var/alien) if(alien == IS_DIONA) return @@ -757,6 +764,7 @@ id = "rewriter" color = "#485000" adj_temp = -5 + caffeine = 0.4 glass_icon_state = "rewriter" glass_name = "glass of Rewriter" @@ -767,6 +775,7 @@ ..() M.make_jittery(5) + /datum/reagent/drink/nuka_cola name = "Nuka Cola" id = "nuka_cola" @@ -774,6 +783,7 @@ color = "#100800" adj_temp = -5 adj_sleepy = -2 + caffeine = 1 glass_icon_state = "nuka_colaglass" glass_name = "glass of Nuka-Cola" @@ -1043,6 +1053,7 @@ description = "A widely known, Mexican coffee-flavoured liqueur. In production since 1936!" color = "#664300" strength = 20 + caffeine = 0.25 glass_icon_state = "kahluaglass" glass_name = "glass of RR coffee liquor" @@ -1115,6 +1126,7 @@ color = "#102000" strength = 10 nutriment_factor = 1 + caffeine = 0.5 glass_icon_state = "thirteen_loko_glass" glass_name = "glass of Thirteen Loko" @@ -1390,6 +1402,7 @@ description = "It's just as effective as Dutch-Courage!" color = "#664300" strength = 30 + caffeine = 0.2 glass_icon_state = "bravebullglass" glass_name = "glass of Brave Bull" @@ -1598,6 +1611,7 @@ description = "Coffee, and alcohol. More fun than a Mimosa to drink in the morning." color = "#664300" strength = 15 + caffeine = 0.3 glass_icon_state = "irishcoffeeglass" glass_name = "glass of Irish coffee" @@ -1979,6 +1993,7 @@ adj_sleepy = -3 adj_temp = 30 overdose = 40 + caffeine = 0.4 glass_icon_state = "blackcoffee" glass_name = "A mug of rich Black Coffee" @@ -1994,6 +2009,7 @@ adj_sleepy = -3 adj_temp = 30 overdose = 40 + caffeine = 0.3 glass_icon_state = "whitecoffee" glass_name = "A mug of Café Au Lait" @@ -2013,6 +2029,7 @@ adj_sleepy = -3 adj_temp = 30 overdose = 40 + caffeine = 0.3 glass_icon_state = "whitecoffee" glass_name = "A mug of Café Mélange" diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Medicine.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Medicine.dm index 08b276b7021..187d226a3eb 100644 --- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Medicine.dm +++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Medicine.dm @@ -9,11 +9,15 @@ overdose = REAGENTS_OVERDOSE * 2 metabolism = REM * 0.5 scannable = 1 + var/datum/modifier/modifier /datum/reagent/inaprovaline/affect_blood(var/mob/living/carbon/M, var/alien, var/removed) if(alien != IS_DIONA) M.add_chemical_effect(CE_STABLE) M.add_chemical_effect(CE_PAINKILLER, 25) + if (!modifier) + modifier = M.add_modifier(/datum/modifier/adrenaline, MODIFIER_REAGENT, src, _strength = 0.6, override = MODIFIER_OVERRIDE_STRENGTHEN) + /datum/reagent/bicaridine name = "Bicaridine" @@ -212,6 +216,7 @@ metabolism = REM * 0.05 overdose = REAGENTS_OVERDOSE scannable = 1 + var/datum/modifier/modifier /datum/reagent/synaptizine/affect_blood(var/mob/living/carbon/M, var/alien, var/removed) if(alien == IS_DIONA) @@ -224,6 +229,10 @@ M.hallucination = max(0, M.hallucination - 10) M.adjustToxLoss(5 * removed) // It used to be incredibly deadly due to an oversight. Not anymore! M.add_chemical_effect(CE_PAINKILLER, 40) + if (!modifier) + modifier = M.add_modifier(/datum/modifier/adrenaline, MODIFIER_REAGENT, src, _strength = 1, override = MODIFIER_OVERRIDE_STRENGTHEN) + + /datum/reagent/alkysine name = "Alkysine" @@ -305,6 +314,7 @@ color = "#FF3300" metabolism = REM * 0.15 overdose = REAGENTS_OVERDOSE * 0.5 + var/datum/modifier = null /datum/reagent/hyperzine/affect_blood(var/mob/living/carbon/M, var/alien, var/removed) if(alien == IS_DIONA) @@ -312,7 +322,8 @@ if(prob(5)) M.emote(pick("twitch", "blink_r", "shiver")) M.add_chemical_effect(CE_SPEEDBOOST, 1) - + if (!modifier) + modifier = M.add_modifier(/datum/modifier/stimulant, MODIFIER_REAGENT, src, _strength = 1, override = MODIFIER_OVERRIDE_STRENGTHEN) #define ETHYL_INTOX_COST 3 //The cost of power to remove one unit of intoxication from the patient diff --git a/html/changelogs/Nanako-Sprinting.yml b/html/changelogs/Nanako-Sprinting.yml new file mode 100644 index 00000000000..0e65761d7ab --- /dev/null +++ b/html/changelogs/Nanako-Sprinting.yml @@ -0,0 +1,40 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# maptweak +# spellcheck (typo fixes) +# experiment +################################# + +# Your name. +author: Nanako + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries. +# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog. +changes: + - rscadd: "Added a new sprinting mechanic. Moving in run mode is now much faster, but limited by stamina or a special species mechanic. Sprinting works slightly differently for each species." + - tweak: "Moving in walk mode is now as fast as run used to be. Walk is the new default speed." + - rscadd: "Added a walkspeed limiting feature. Use Limit Walk Speed in the IC tab, or alt+click on the walk/run button to bring up a menu. This allows limiting your walk speed very precisely to any value below normal. It can only slow you down, will not increase speed." + - tweak: "Natural recovery of suffocation damage is now slower." + - rscadd: "Alcohol, caffienated drinks, and several performance enhancing drugs now have interactions with movement, sprinting and stamina." diff --git a/icons/mob/screen1.dmi b/icons/mob/screen1.dmi index 8181013f7ee1be7be12bec1c7496197e1c2a0c74..3e9624536f6629a1de7148abed0153a3c1d1e690 100644 GIT binary patch delta 13853 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 z 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 rzt*Jeb2q4qu>fcn0R}_XgkB$!@a;FMLdb(5