Merge pull request #539 from ArchieBeepBoop/mobholders

Enter the Elements System
This commit is contained in:
Dahlular
2020-10-02 17:00:15 -06:00
committed by GitHub
94 changed files with 1713 additions and 1356 deletions
+11
View File
@@ -141,6 +141,17 @@
current_button.add_overlay(mutable_appearance(icon_icon, button_icon_state))
current_button.button_icon_state = button_icon_state
/datum/action/ghost
icon_icon = 'icons/mob/mob.dmi'
button_icon_state = "ghost"
name = "Ghostize"
desc = "Turn into a ghost and freely come back to your body."
/datum/action/ghost/Trigger()
if(!..())
return 0
var/mob/M = target
M.ghostize(1)
//Presets for item actions
/datum/action/item_action
-30
View File
@@ -1,30 +0,0 @@
// An item worn in the ear slot with this component will heal your ears each
// Life() tick, even if normally your ears would be too damaged to heal.
/datum/component/earhealing
var/mob/living/carbon/wearer
/datum/component/earhealing/Initialize()
if(!isitem(parent))
return COMPONENT_INCOMPATIBLE
RegisterSignal(parent, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED), .proc/equippedChanged)
/datum/component/earhealing/proc/equippedChanged(datum/source, mob/living/carbon/user, slot)
if (slot == SLOT_EARS && istype(user))
if (!wearer)
START_PROCESSING(SSobj, src)
wearer = user
else
if (wearer)
STOP_PROCESSING(SSobj, src)
wearer = null
/datum/component/earhealing/process()
if (!wearer)
STOP_PROCESSING(SSobj, src)
return
if(!HAS_TRAIT(wearer, TRAIT_DEAF))
var/obj/item/organ/ears/ears = wearer.getorganslot(ORGAN_SLOT_EARS)
if (ears)
ears.deaf = max(ears.deaf - 1, (ears.damage < ears.maxHealth ? 0 : 1)) // Do not clear deafness if our ears are too damaged
ears.damage = max(ears.damage - 0.1, 0)
+1 -1
View File
@@ -280,7 +280,7 @@
var/datum/hud/hud = owner.hud_used
screen_obj = new
hud.infodisplay += screen_obj
RegisterSignal(hud, COMSIG_PARENT_QDELETED, .proc/unmodify_hud)
RegisterSignal(hud, COMSIG_PARENT_QDELETING, .proc/unmodify_hud)
RegisterSignal(screen_obj, COMSIG_CLICK, .proc/hud_click)
/datum/component/mood/proc/unmodify_hud(datum/source)
+39
View File
@@ -0,0 +1,39 @@
/datum/element
var/element_flags = NONE
/**
* The index of the first attach argument to consider for duplicate elements
* Is only used when flags contains ELEMENT_BESPOKE
* This is infinity so you must explicitly set this
*/
var/id_arg_index = INFINITY
/datum/element/proc/Attach(datum/target)
if(type == /datum/element)
return ELEMENT_INCOMPATIBLE
if(element_flags & ELEMENT_DETACH)
RegisterSignal(target, COMSIG_PARENT_QDELETING, .proc/Detach, override = TRUE)
/datum/element/proc/Detach(datum/source, force)
UnregisterSignal(source, COMSIG_PARENT_QDELETING)
/datum/element/Destroy(force)
if(!force)
return QDEL_HINT_LETMELIVE
SSdcs.elements_by_type -= type
return ..()
//DATUM PROCS
/datum/proc/AddElement(eletype, ...)
var/datum/element/ele = SSdcs.GetElement(arglist(args))
args[1] = src
if(ele.Attach(arglist(args)) == ELEMENT_INCOMPATIBLE)
CRASH("Incompatible [eletype] assigned to a [type]! args: [json_encode(args)]")
/**
* Finds the singleton for the element type given and detaches it from src
* You only need additional arguments beyond the type if you're using ELEMENT_BESPOKE
*/
/datum/proc/RemoveElement(eletype, ...)
var/datum/element/ele = SSdcs.GetElement(arglist(args))
ele.Detach(src)
@@ -1,19 +1,18 @@
/datum/component/cleaning
dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
/datum/element/cleaning/Attach(datum/target)
. = ..()
if(!ismovableatom(target))
return ELEMENT_INCOMPATIBLE
RegisterSignal(target, COMSIG_MOVABLE_MOVED, .proc/Clean)
/datum/component/cleaning/Initialize()
if(!ismovableatom(parent))
return COMPONENT_INCOMPATIBLE
RegisterSignal(parent, list(COMSIG_MOVABLE_MOVED), .proc/Clean)
/datum/element/cleaning/Detach(datum/target)
. = ..()
UnregisterSignal(target, COMSIG_MOVABLE_MOVED)
/datum/component/cleaning/proc/Clean()
var/atom/movable/AM = parent
var/turf/tile = AM.loc
if(!isturf(tile))
return
SEND_SIGNAL(tile, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRENGTH_BLOOD)
for(var/A in tile)
/datum/element/cleaning/proc/Clean(datum/source)
var/atom/movable/AM = source
var/turf/T = AM.loc
SEND_SIGNAL(T, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_WEAK)
for(var/A in T)
if(is_cleanable(A))
qdel(A)
else if(istype(A, /obj/item))
@@ -36,4 +35,4 @@
SEND_SIGNAL(cleaned_human, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRENGTH_BLOOD)
cleaned_human.wash_cream()
cleaned_human.regenerate_icons()
to_chat(cleaned_human, "<span class='danger'>[AM] cleans your face!</span>")
to_chat(cleaned_human, "<span class='danger'>[src] cleans your face!</span>")
+37
View File
@@ -0,0 +1,37 @@
/datum/element/earhealing
element_flags = ELEMENT_DETACH
var/list/user_by_item = list()
/datum/element/earhealing/New()
START_PROCESSING(SSdcs, src)
/datum/element/earhealing/Attach(datum/target)
. = ..()
if(!isitem(target))
return ELEMENT_INCOMPATIBLE
RegisterSignal(target, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED), .proc/equippedChanged)
/datum/element/earhealing/Detach(datum/target)
. = ..()
UnregisterSignal(target, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED))
user_by_item -= target
/datum/element/earhealing/proc/equippedChanged(datum/source, mob/living/carbon/user, slot)
if(slot == SLOT_EARS && istype(user))
user_by_item[source] = user
else
user_by_item -= source
/datum/element/earhealing/process()
for(var/i in user_by_item)
var/mob/living/carbon/user = user_by_item[i]
if(HAS_TRAIT(user, TRAIT_DEAF))
continue
var/obj/item/organ/ears/ears = user.getorganslot(ORGAN_SLOT_EARS)
if(!ears)
continue
ears.deaf = max(ears.deaf - 0.25, (ears.damage < ears.maxHealth ? 0 : 1)) // Do not clear deafness if our ears are too damaged
ears.damage = max(ears.damage - 0.025, 0)
CHECK_TICK
@@ -0,0 +1,56 @@
/datum/element/ghost_role_eligibility
element_flags = ELEMENT_DETACH
var/list/timeouts = list()
var/list/mob/eligible_mobs = list()
/datum/element/ghost_role_eligibility/Attach(datum/target,penalize = FALSE)
. = ..()
if(!ismob(target))
return ELEMENT_INCOMPATIBLE
var/mob/M = target
if(!(M in eligible_mobs))
eligible_mobs += M
if(penalize) //penalizing them from making a ghost role / midround antag comeback right away.
var/penalty = CONFIG_GET(number/suicide_reenter_round_timer) MINUTES
var/roundstart_quit_limit = CONFIG_GET(number/roundstart_suicide_time_limit) MINUTES
if(world.time < roundstart_quit_limit) //add up the time difference to their antag rolling penalty if they quit before half a (ingame) hour even passed.
penalty += roundstart_quit_limit - world.time
if(penalty)
penalty += world.realtime
if(penalty - SSshuttle.realtimeofstart > SSshuttle.auto_call + SSshuttle.emergencyCallTime + SSshuttle.emergencyDockTime + SSshuttle.emergencyEscapeTime)
penalty = CANT_REENTER_ROUND
if(!(M.ckey in timeouts))
timeouts += M.ckey
timeouts[M.ckey] = 0
else if(timeouts[M.ckey] == CANT_REENTER_ROUND)
return
timeouts[M.ckey] = max(timeouts[M.ckey],penalty)
/datum/element/ghost_role_eligibility/Detach(mob/M)
. = ..()
if(M in eligible_mobs)
eligible_mobs -= M
/datum/element/ghost_role_eligibility/proc/get_all_ghost_role_eligible(silent = FALSE)
var/list/candidates = list()
for(var/m in eligible_mobs)
var/mob/M = m
if(M.can_reenter_round(TRUE))
candidates += M
return candidates
/mob/proc/can_reenter_round(silent = FALSE)
var/datum/element/ghost_role_eligibility/eli = SSdcs.GetElement(/datum/element/ghost_role_eligibility)
return eli.can_reenter_round(src,silent)
/datum/element/ghost_role_eligibility/proc/can_reenter_round(var/mob/M,silent = FALSE)
if(!(M in eligible_mobs))
return FALSE
if(!(M.ckey in timeouts))
return TRUE
var/timeout = timeouts[M.ckey]
if(timeout != CANT_REENTER_ROUND && timeout <= world.realtime)
return TRUE
if(!silent && M.client)
to_chat(M, "<span class='warning'>You are unable to reenter the round[timeout != CANT_REENTER_ROUND ? " yet. Your ghost role blacklist will expire in [DisplayTimeText(timeout - world.realtime)]" : ""].</span>")
return FALSE
+187
View File
@@ -0,0 +1,187 @@
/datum/element/mob_holder
element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH
id_arg_index = 2
var/worn_state
var/alt_worn
var/right_hand
var/left_hand
var/inv_slots
var/proctype //if present, will be invoked on headwear generation.
/datum/element/mob_holder/Attach(datum/target, _worn_state, _alt_worn, _right_hand, _left_hand, _inv_slots = NONE, _proctype)
. = ..()
if(!isliving(target))
return ELEMENT_INCOMPATIBLE
worn_state = _worn_state
alt_worn = _alt_worn
right_hand = _right_hand
left_hand = _left_hand
inv_slots = _inv_slots
proctype = _proctype
RegisterSignal(target, COMSIG_CLICK_ALT, .proc/mob_try_pickup)
RegisterSignal(target, COMSIG_PARENT_EXAMINE, .proc/on_examine)
/datum/element/mob_holder/Detach(datum/source, force)
. = ..()
UnregisterSignal(source, COMSIG_CLICK_ALT)
UnregisterSignal(source, COMSIG_PARENT_EXAMINE)
/datum/element/mob_holder/proc/on_examine(mob/living/source, mob/user, list/examine_list)
if(ishuman(user) && !istype(source.loc, /obj/item/clothing/head/mob_holder))
examine_list += "<span class='notice'>Looks like [source.p_they(TRUE)] can be picked up with <b>Alt+Click</b>!</span>"
/datum/element/mob_holder/proc/mob_try_pickup(mob/living/source, mob/user)
if(!ishuman(user) || !user.Adjacent(source) || user.incapacitated())
return FALSE
if(user.get_active_held_item())
to_chat(user, "<span class='warning'>Your hands are full!</span>")
return FALSE
if(source.buckled)
to_chat(user, "<span class='warning'>[source] is buckled to something!</span>")
return FALSE
if(source == user)
to_chat(user, "<span class='warning'>You can't pick yourself up.</span>")
return FALSE
source.visible_message("<span class='warning'>[user] starts picking up [source].</span>", \
"<span class='userdanger'>[user] starts picking you up!</span>")
if(!do_after(user, 20, target = source) || source.buckled)
return FALSE
source.visible_message("<span class='warning'>[user] picks up [source]!</span>", \
"<span class='userdanger'>[user] picks you up!</span>")
to_chat(user, "<span class='notice'>You pick [source] up.</span>")
source.drop_all_held_items()
var/obj/item/clothing/head/mob_holder/holder = new(get_turf(source), source, worn_state, alt_worn, right_hand, left_hand, inv_slots)
if(proctype)
INVOKE_ASYNC(src, proctype, source, holder, user)
user.put_in_hands(holder)
return TRUE
/datum/element/mob_holder/proc/drone_worn_icon(mob/living/simple_animal/drone/D, obj/item/clothing/head/mob_holder/holder, mob/user)
var/new_state = "[D.visualAppearence]_hat"
holder.item_state = new_state
holder.icon_state = new_state
//The item itself,
/obj/item/clothing/head/mob_holder
name = "bugged mob"
desc = "Yell at coderbrush."
icon = null
alternate_worn_icon = 'icons/mob/animals_held.dmi'
righthand_file = 'icons/mob/animals_held_rh.dmi'
lefthand_file = 'icons/mob/animals_held_lh.dmi'
icon_state = ""
w_class = WEIGHT_CLASS_BULKY
var/mob/living/held_mob
var/can_head = FALSE
/obj/item/clothing/head/mob_holder/Initialize(mapload, mob/living/target, worn_state, alt_worn, right_hand, left_hand, slots = NONE)
. = ..()
if(target)
assimilate(target)
if(alt_worn)
alternate_worn_icon = alt_worn
if(worn_state)
item_state = worn_state
icon_state = worn_state
if(left_hand)
lefthand_file = left_hand
if(right_hand)
righthand_file = right_hand
slot_flags = slots
/obj/item/clothing/head/mob_holder/proc/assimilate(mob/living/target)
target.setDir(SOUTH)
held_mob = target
target.forceMove(src)
var/image/I = new //work around to retain the same appearance to the mob idependently from inhands/worn states.
I.appearance = target.appearance
I.override = TRUE
add_overlay(I)
name = target.name
desc = target.desc
switch(target.mob_size)
if(MOB_SIZE_TINY)
w_class = WEIGHT_CLASS_TINY
if(MOB_SIZE_SMALL)
w_class = WEIGHT_CLASS_NORMAL
if(MOB_SIZE_HUMAN)
w_class = WEIGHT_CLASS_BULKY
if(MOB_SIZE_LARGE)
w_class = WEIGHT_CLASS_HUGE
RegisterSignal(src, COMSIG_CLICK_SHIFT, .proc/examine_held_mob, override = TRUE)
/obj/item/clothing/head/mob_holder/Destroy()
if(held_mob)
release()
return ..()
/obj/item/clothing/head/mob_holder/proc/examine_held_mob(datum/source, mob/user)
held_mob.ShiftClick(user)
return COMPONENT_DENY_EXAMINATE
/obj/item/clothing/head/mob_holder/Exited(atom/movable/AM, atom/newloc)
. = ..()
if(AM == held_mob)
held_mob.reset_perspective()
held_mob = null
qdel(src)
/obj/item/clothing/head/mob_holder/Entered(atom/movable/AM, atom/newloc)
. = ..()
if(AM != held_mob)
var/destination = loc
if(isliving(loc)) //the mob is held or worn, drop things on the floor
destination = get_turf(loc)
AM.forceMove(destination)
/obj/item/clothing/head/mob_holder/dropped()
. = ..()
if(held_mob && isturf(loc))//don't release on soft-drops
release()
/obj/item/clothing/head/mob_holder/proc/release()
if(held_mob)
var/mob/living/L = held_mob
held_mob = null
L.forceMove(get_turf(L))
L.reset_perspective()
L.setDir(SOUTH)
qdel(src)
/obj/item/clothing/head/mob_holder/relaymove(mob/user)
return
/obj/item/clothing/head/mob_holder/container_resist()
if(isliving(loc))
var/mob/living/L = loc
L.visible_message("<span class='warning'>[src] escapes from [L]!</span>", "<span class='warning'>[src] escapes your grip!</span>")
release()
/obj/item/clothing/head/mob_holder/assume_air(datum/gas_mixture/env)
var/atom/location = loc
if(!loc)
return //null
var/turf/T = get_turf(loc)
while(location != T)
location = location.loc
if(ismob(location))
return location.loc.assume_air(env)
return loc.assume_air(env)
/obj/item/clothing/head/mob_holder/remove_air(amount)
var/atom/location = loc
if(!loc)
return //null
var/turf/T = get_turf(loc)
while(location != T)
location = location.loc
if(ismob(location))
return location.loc.remove_air(amount)
return loc.remove_air(amount)
+64
View File
@@ -0,0 +1,64 @@
/datum/element/wuv //D'awwwww
element_flags = ELEMENT_BESPOKE
id_arg_index = 2
//the for the me emote proc call when petted.
var/pet_emote
//whether the emote is visible or audible
var/pet_type
//same as above, except when harmed. "You are going into orbit, you stupid mutt!"
var/punt_emote
//same as pet_type
var/punt_type
//mood typepath for the moodlet signal when petted.
var/pet_moodlet
//same as above but for harm
var/punt_moodlet
/datum/element/wuv/Attach(datum/target, pet, pet_t, pet_mood, punt, punt_t, punt_mood)
. = ..()
if(!isliving(target))
return ELEMENT_INCOMPATIBLE
pet_emote = pet
pet_type = pet_t
punt_emote = punt
punt_type = punt_t
pet_moodlet = pet_mood
punt_moodlet = punt_mood
RegisterSignal(target, COMSIG_MOB_ATTACK_HAND, .proc/on_attack_hand)
/datum/element/wuv/Detach(datum/source, force)
. = ..()
UnregisterSignal(source, COMSIG_MOB_ATTACK_HAND)
/datum/element/wuv/proc/on_attack_hand(datum/source, mob/user)
var/mob/living/L = source
if(L.stat == DEAD)
return
//we want to delay the effect to be displayed after the mob is petted, not before.
switch(user.a_intent)
if(INTENT_HARM, INTENT_DISARM)
addtimer(CALLBACK(src, .proc/kick_the_dog, source, user), 1)
if(INTENT_HELP)
addtimer(CALLBACK(src, .proc/pet_the_dog, source, user), 1)
/datum/element/wuv/proc/pet_the_dog(mob/target, mob/user)
if(QDELETED(target) || QDELETED(user) || target.stat != CONSCIOUS)
return
new /obj/effect/temp_visual/heart(target.loc)
if(pet_emote)
target.emote("me", pet_type, pet_emote)
if(pet_moodlet && !CHECK_BITFIELD(target.flags_1, HOLOGRAM_1)) //prevents unlimited happiness petting park exploit.
SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, target, pet_moodlet, target)
/datum/element/wuv/proc/kick_the_dog(mob/target, mob/user)
if(QDELETED(target) || QDELETED(user) || target.stat != CONSCIOUS)
return
if(punt_emote)
target.emote("me", punt_type, punt_emote)
if(punt_moodlet && !CHECK_BITFIELD(target.flags_1, HOLOGRAM_1))
SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, target, punt_moodlet, target)
+1
View File
@@ -79,6 +79,7 @@
tele_play_specials(teleatom, destturf, effectout, asoundout)
if(ismegafauna(teleatom))
message_admins("[teleatom] [ADMIN_FLW(teleatom)] has teleported from [ADMIN_VERBOSEJMP(curturf)] to [ADMIN_VERBOSEJMP(destturf)].")
SEND_SIGNAL(teleatom, COMSIG_MOVABLE_TELEPORTED, channel, curturf, destturf)
if(ismob(teleatom))
var/mob/M = teleatom
+1
View File
@@ -10,6 +10,7 @@
var/block_chance = 0 //Chance to block melee attacks using items while on throw mode.
var/restraining = 0 //used in cqc's disarm_act to check if the disarmed is being restrained and so whether they should be put in a chokehold or not
var/help_verb
var/pacifism_check = TRUE //are the martial arts combos/attacks unable to be used by pacifist.
var/no_guns = FALSE
var/allow_temp_override = TRUE //if this martial art can be overridden by temporary martial arts
+2
View File
@@ -1,6 +1,8 @@
/datum/martial_art/boxing
name = "Boxing"
id = MARTIALART_BOXING
pacifism_check = FALSE //Let's pretend pacifists can boxe the heck out of other people, it only deals stamina damage right now.
/datum/martial_art/boxing/disarm_act(mob/living/carbon/human/A, mob/living/carbon/human/D)
to_chat(A, "<span class='warning'>Can't disarm while boxing!</span>")
+2 -1
View File
@@ -1,6 +1,7 @@
/datum/martial_art/psychotic_brawling
name = "Psychotic Brawling"
id = MARTIALART_PSYCHOBRAWL
pacifism_check = FALSE //Quite uncontrollable and unpredictable, people will still end up harming others with it.
/datum/martial_art/psychotic_brawling/disarm_act(mob/living/carbon/human/A, mob/living/carbon/human/D)
return psycho_attack(A,D)
@@ -64,4 +65,4 @@
if(atk_verb)
log_combat(A, D, "[atk_verb] (Psychotic Brawling)")
return 1
return 1
@@ -23,10 +23,13 @@
mood_change = 3
timeout = 3000
/datum/mood_event/pet_corgi
description = "<span class='nicegreen'>Corgis are adorable! I can't stop petting them!</span>\n"
mood_change = 3
timeout = 3000
/datum/mood_event/pet_animal
description = "<span class='nicegreen'>Animals are adorable! I can't stop petting them!</span>\n"
mood_change = 2
timeout = 5 MINUTES
/datum/mood_event/pet_animal/add_effects(mob/animal)
description = "<span class='nicegreen'>\The [animal.name] is adorable! I can't stop petting [animal.p_them()]!</span>\n"
/datum/mood_event/honk
description = "<span class='nicegreen'>Maybe clowns aren't so bad after all. Honk!</span>\n"