Adds a knockedout trait, begins refactoring mobility 2.0 (#51348)

* ko

* change request
This commit is contained in:
Rohesie
2020-06-01 19:14:49 -03:00
committed by GitHub
parent 0a8790d4d6
commit b73e2c4b0e
24 changed files with 166 additions and 68 deletions
+3
View File
@@ -76,6 +76,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
*/
//mob traits
#define TRAIT_KNOCKEDOUT "knockedout" //Forces the user to stay unconscious.
#define TRAIT_INCAPACITATED "incapacitated"
#define TRAIT_BLIND "blind"
#define TRAIT_MUTE "mute"
@@ -257,6 +258,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define GLASSES_TRAIT "glasses"
#define VEHICLE_TRAIT "vehicle" // inherited from riding vehicles
#define INNATE_TRAIT "innate"
#define CRIT_HEALTH_TRAIT "crit_health"
#define OXYLOSS_TRAIT "oxyloss"
// unique trait sources, still defines
#define CLONING_POD_TRAIT "cloning-pod"
+1 -1
View File
@@ -14,7 +14,7 @@
if(check_click_intercept(params,A))
return
if(stat || lockcharge || IsParalyzed() || IsStun() || IsUnconscious())
if(stat || lockcharge || IsParalyzed() || IsStun())
return
var/list/modifiers = params2list(params)
@@ -267,20 +267,19 @@
var/mob/living/M = A.affected_mob
if(HAS_TRAIT(M, TRAIT_DEATHCOMA))
return power
else if(M.IsUnconscious() || M.stat == UNCONSCIOUS)
if(M.IsSleeping())
return power * 0.25 //Voluntary unconsciousness yields lower healing.
if(M.stat == UNCONSCIOUS)
return power * 0.9
else if(M.stat == SOFT_CRIT)
if(M.stat == SOFT_CRIT)
return power * 0.5
else if(M.IsSleeping())
return power * 0.25
else if(M.getBruteLoss() + M.getFireLoss() >= 70 && !active_coma)
if(M.getBruteLoss() + M.getFireLoss() >= 70 && !active_coma)
to_chat(M, "<span class='warning'>You feel yourself slip into a regenerative coma...</span>")
active_coma = TRUE
addtimer(CALLBACK(src, .proc/coma, M), 60)
/datum/symptom/heal/coma/proc/coma(mob/living/M)
M.fakedeath("regenerative_coma", !deathgasp)
M.update_stat()
M.update_mobility()
addtimer(CALLBACK(src, .proc/uncoma, M), 300)
@@ -289,7 +288,6 @@
return
active_coma = FALSE
M.cure_fakedeath("regenerative_coma")
M.update_stat()
M.update_mobility()
/datum/symptom/heal/coma/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power)
+12 -2
View File
@@ -68,10 +68,10 @@
. = ..()
if(!.)
return
ADD_TRAIT(owner, TRAIT_INCAPACITATED, TRAIT_STATUS_EFFECT(id))
ADD_TRAIT(owner, TRAIT_KNOCKEDOUT, TRAIT_STATUS_EFFECT(id))
/datum/status_effect/incapacitating/unconscious/on_remove()
REMOVE_TRAIT(owner, TRAIT_INCAPACITATED, TRAIT_STATUS_EFFECT(id))
REMOVE_TRAIT(owner, TRAIT_KNOCKEDOUT, TRAIT_STATUS_EFFECT(id))
return ..()
/datum/status_effect/incapacitating/unconscious/tick()
@@ -99,6 +99,16 @@
human_owner = null
return ..()
/datum/status_effect/incapacitating/sleeping/on_apply()
. = ..()
if(!.)
return
ADD_TRAIT(owner, TRAIT_KNOCKEDOUT, TRAIT_STATUS_EFFECT(id))
/datum/status_effect/incapacitating/sleeping/on_remove()
REMOVE_TRAIT(owner, TRAIT_KNOCKEDOUT, TRAIT_STATUS_EFFECT(id))
return ..()
/datum/status_effect/incapacitating/sleeping/tick()
if(owner.maxHealth)
var/health_ratio = owner.health / owner.maxHealth
@@ -24,7 +24,6 @@
else
to_chat(user, "<span class='notice'>We begin our stasis, preparing energy to arise once more.</span>")
user.fakedeath("changeling") //play dead
user.update_stat()
user.update_mobility()
addtimer(CALLBACK(src, .proc/ready_to_regenerate, user), LING_FAKEDEATH_TIME, TIMER_UNIQUE)
return TRUE
+1 -1
View File
@@ -355,7 +355,7 @@
if(target.get_item_by_slot(ITEM_SLOT_HEAD) != src)
return
QDEL_NULL(paranoia)
if(!target.IsUnconscious())
if(target.stat < UNCONSCIOUS)
to_chat(target, "<span class='warning'>Your zealous conspirationism rapidly dissipates as the donned hat warps up into a ruined mess. All those theories starting to sound like nothing but a ridicolous fanfare.</span>")
/obj/item/clothing/head/foilhat/attack_hand(mob/user)
@@ -9,6 +9,7 @@
maxHealth = 25
health = 25
hardcrit_threshold = HEALTH_THRESHOLD_CRIT
var/amount_grown = 0
var/max_grown = 100
@@ -18,15 +18,12 @@
if(health<= -maxHealth || !getorgan(/obj/item/organ/brain))
death()
return
if(IsUnconscious() || IsSleeping() || getOxyLoss() > 50 || (HAS_TRAIT(src, TRAIT_DEATHCOMA)) || health <= crit_threshold)
if(stat == CONSCIOUS)
set_stat(UNCONSCIOUS)
become_blind(UNCONSCIOUS_BLIND)
update_mobility()
if((HAS_TRAIT(src, TRAIT_KNOCKEDOUT)))
set_stat(UNCONSCIOUS)
else
if(stat == UNCONSCIOUS)
set_stat(CONSCIOUS)
cure_blind(UNCONSCIOUS_BLIND)
set_resting(FALSE)
set_stat(CONSCIOUS)
update_mobility()
update_damage_hud()
update_health_hud()
+19 -10
View File
@@ -514,7 +514,7 @@
total_brute += (BP.brute_dam * BP.body_damage_coeff)
total_burn += (BP.burn_dam * BP.body_damage_coeff)
total_stamina += (BP.stamina_dam * BP.stam_damage_coeff)
health = round(maxHealth - getOxyLoss() - getToxLoss() - getCloneLoss() - total_burn - total_brute, DAMAGE_PRECISION)
set_health(round(maxHealth - getOxyLoss() - getToxLoss() - getCloneLoss() - total_burn - total_brute, DAMAGE_PRECISION))
staminaloss = round(total_stamina, DAMAGE_PRECISION)
update_stat()
update_mobility()
@@ -756,28 +756,37 @@
if(hud_used && hud_used.spacesuit)
hud_used.spacesuit.icon_state = "spacesuit_[cell_state]"
/mob/living/carbon/set_health(new_value)
. = ..()
if(. > hardcrit_threshold)
if(health <= hardcrit_threshold && !HAS_TRAIT(src, TRAIT_NOHARDCRIT))
ADD_TRAIT(src, TRAIT_KNOCKEDOUT, CRIT_HEALTH_TRAIT)
else if(health > hardcrit_threshold)
REMOVE_TRAIT(src, TRAIT_KNOCKEDOUT, CRIT_HEALTH_TRAIT)
if(CONFIG_GET(flag/near_death_experience))
if(. > HEALTH_THRESHOLD_NEARDEATH)
if(health <= HEALTH_THRESHOLD_NEARDEATH && !HAS_TRAIT(src, TRAIT_NODEATH))
ADD_TRAIT(src, TRAIT_SIXTHSENSE, "near-death")
else if(health > HEALTH_THRESHOLD_NEARDEATH)
REMOVE_TRAIT(src, TRAIT_SIXTHSENSE, "near-death")
/mob/living/carbon/update_stat()
if(status_flags & GODMODE)
return
if(stat != DEAD)
if(health <= HEALTH_THRESHOLD_DEAD && !HAS_TRAIT(src, TRAIT_NODEATH))
death()
cure_blind(UNCONSCIOUS_BLIND)
return
if(IsUnconscious() || IsSleeping() || getOxyLoss() > 50 || (HAS_TRAIT(src, TRAIT_DEATHCOMA)) || (health <= HEALTH_THRESHOLD_FULLCRIT && !HAS_TRAIT(src, TRAIT_NOHARDCRIT)))
if(HAS_TRAIT(src, TRAIT_KNOCKEDOUT))
set_stat(UNCONSCIOUS)
become_blind(UNCONSCIOUS_BLIND)
if(CONFIG_GET(flag/near_death_experience) && health <= HEALTH_THRESHOLD_NEARDEATH && !HAS_TRAIT(src, TRAIT_NODEATH))
ADD_TRAIT(src, TRAIT_SIXTHSENSE, "near-death")
else
REMOVE_TRAIT(src, TRAIT_SIXTHSENSE, "near-death")
else
if(health <= crit_threshold && !HAS_TRAIT(src, TRAIT_NOSOFTCRIT))
set_stat(SOFT_CRIT)
else
set_stat(CONSCIOUS)
cure_blind(UNCONSCIOUS_BLIND)
REMOVE_TRAIT(src, TRAIT_SIXTHSENSE, "near-death")
update_mobility()
update_damage_hud()
update_health_hud()
@@ -431,3 +431,25 @@
var/obj/item/organ/ears/ears = getorganslot(ORGAN_SLOT_EARS)
if(istype(ears) && !ears.deaf)
. = TRUE
/mob/living/carbon/adjustOxyLoss(amount, updating_health = TRUE, forced = FALSE)
. = ..()
if(isnull(.))
return
if(. <= 50)
if(getOxyLoss() > 50)
ADD_TRAIT(src, TRAIT_KNOCKEDOUT, OXYLOSS_TRAIT)
else if(getOxyLoss() <= 50)
REMOVE_TRAIT(src, TRAIT_KNOCKEDOUT, OXYLOSS_TRAIT)
/mob/living/carbon/setOxyLoss(amount, updating_health = TRUE, forced = FALSE)
. = ..()
if(isnull(.))
return
if(. <= 50)
if(getOxyLoss() > 50)
ADD_TRAIT(src, TRAIT_KNOCKEDOUT, OXYLOSS_TRAIT)
else if(getOxyLoss() <= 50)
REMOVE_TRAIT(src, TRAIT_KNOCKEDOUT, OXYLOSS_TRAIT)
@@ -54,8 +54,6 @@
return 1
if(health <= 0 && checkDead)
return 1
if(IsUnconscious())
return 1
if(IsStun() || IsParalyzed())
return 1
if(stat)
@@ -64,7 +64,7 @@
"<span class='danger'>You avoid [M]'s punch!</span>", "<span class='hear'>You hear a swoosh!</span>", COMBAT_MESSAGE_RANGE, M)
to_chat(M, "<span class='warning'>Your punch misses [name]!</span>")
if("disarm")
if(!IsUnconscious())
if(stat < UNCONSCIOUS)
M.do_attack_animation(src, ATTACK_EFFECT_DISARM)
if (prob(25))
Paralyze(40)
+7 -5
View File
@@ -167,19 +167,21 @@
/mob/living/proc/adjustOxyLoss(amount, updating_health = TRUE, forced = FALSE)
if(!forced && (status_flags & GODMODE))
return FALSE
return
. = oxyloss
oxyloss = clamp((oxyloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, maxHealth * 2)
if(updating_health)
updatehealth()
return amount
/mob/living/proc/setOxyLoss(amount, updating_health = TRUE, forced = FALSE)
if(status_flags & GODMODE)
return 0
if(!forced && status_flags & GODMODE)
return
. = oxyloss
oxyloss = amount
if(updating_health)
updatehealth()
return amount
/mob/living/proc/getToxLoss()
return toxloss
+28
View File
@@ -0,0 +1,28 @@
///Called on /mob/living/Initialize(), for the mob to register to relevant signals.
/mob/living/proc/register_init_signals()
RegisterSignal(src, SIGNAL_ADDTRAIT(TRAIT_KNOCKEDOUT), .proc/on_knockedout_trait_gain)
RegisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_KNOCKEDOUT), .proc/on_knockedout_trait_loss)
RegisterSignal(src, SIGNAL_ADDTRAIT(TRAIT_DEATHCOMA), .proc/on_deathcoma_trait_gain)
RegisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_DEATHCOMA), .proc/on_deathcoma_trait_loss)
///Called when TRAIT_KNOCKEDOUT is added to the mob.
/mob/living/proc/on_knockedout_trait_gain(datum/source)
if(stat < UNCONSCIOUS)
set_stat(UNCONSCIOUS)
///Called when TRAIT_KNOCKEDOUT is removed from the mob.
/mob/living/proc/on_knockedout_trait_loss(datum/source)
if(stat < DEAD)
update_stat()
///Called when TRAIT_DEATHCOMA is added to the mob.
/mob/living/proc/on_deathcoma_trait_gain(datum/source)
ADD_TRAIT(src, TRAIT_KNOCKEDOUT, TRAIT_DEATHCOMA)
///Called when TRAIT_DEATHCOMA is removed from the mob.
/mob/living/proc/on_deathcoma_trait_loss(datum/source)
REMOVE_TRAIT(src, TRAIT_KNOCKEDOUT, TRAIT_DEATHCOMA)
+25 -8
View File
@@ -1,5 +1,6 @@
/mob/living/Initialize()
. = ..()
register_init_signals()
if(unique_name)
set_name()
var/datum/atom_hud/data/human/medical/advanced/medhud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED]
@@ -375,8 +376,6 @@
/mob/living/pointed(atom/A as mob|obj|turf in view(client.view, src))
if(incapacitated())
return FALSE
if(HAS_TRAIT(src, TRAIT_DEATHCOMA))
return FALSE
if(!..())
return FALSE
visible_message("<span class='name'>[src]</span> points at [A].", "<span class='notice'>You point at [A].</span>")
@@ -480,10 +479,17 @@
/mob/living/is_drawable(mob/user, allowmobs = TRUE)
return (allowmobs && reagents && can_inject(user))
///Sets the current mob's health value. Do not call directly if you don't know what you are doing, use the damage procs, instead.
/mob/living/proc/set_health(new_value)
. = health
health = new_value
/mob/living/proc/updatehealth()
if(status_flags & GODMODE)
return
health = maxHealth - getOxyLoss() - getToxLoss() - getFireLoss() - getBruteLoss() - getCloneLoss()
set_health(maxHealth - getOxyLoss() - getToxLoss() - getFireLoss() - getBruteLoss() - getCloneLoss())
staminaloss = getStaminaLoss()
update_stat()
med_hud_set_health()
@@ -895,7 +901,7 @@
if(c.clothing_flags & DANGEROUS_OBJECT)
who.visible_message("<span class='danger'>[src] tries to put [what] on [who].</span>", \
"<span class='userdanger'>[src] tries to put [what] on you.</span>", null, null, src)
else
else
who.visible_message("<span class='notice'>[src] tries to put [what] on [who].</span>", \
"<span class='notice'>[src] tries to put [what] on you.</span>", null, null, src)
to_chat(src, "<span class='notice'>You try to put [what] on [who]...</span>")
@@ -1168,7 +1174,6 @@
/mob/living/proc/update_mobility()
var/stat_softcrit = stat == SOFT_CRIT
var/stat_conscious = (stat == CONSCIOUS) || stat_softcrit
var/conscious = !IsUnconscious() && stat_conscious && !HAS_TRAIT(src, TRAIT_DEATHCOMA)
var/chokehold = pulledby && pulledby.grab_state >= GRAB_NECK
var/restrained = restrained()
var/has_legs = get_num_legs()
@@ -1177,12 +1182,12 @@
var/stun = IsStun()
var/knockdown = IsKnockdown()
var/ignore_legs = get_leg_ignore()
var/canmove = !IsImmobilized() && !stun && conscious && !paralyzed && !buckled && (!stat_softcrit || !pulledby) && !chokehold && !IsFrozen() && !IS_IN_STASIS(src) && (has_arms || ignore_legs || has_legs)
var/canmove = !IsImmobilized() && !stun && stat_conscious && !paralyzed && !buckled && (!stat_softcrit || !pulledby) && !chokehold && !IsFrozen() && !IS_IN_STASIS(src) && (has_arms || ignore_legs || has_legs)
if(canmove)
mobility_flags |= MOBILITY_MOVE
else
mobility_flags &= ~MOBILITY_MOVE
var/canstand_involuntary = conscious && !stat_softcrit && !knockdown && !chokehold && !paralyzed && (ignore_legs || has_legs) && !(buckled && buckled.buckle_lying)
var/canstand_involuntary = stat_conscious && !stat_softcrit && !knockdown && !chokehold && !paralyzed && (ignore_legs || has_legs) && !(buckled && buckled.buckle_lying)
var/canstand = canstand_involuntary && !resting
if(buckled && buckled.buckle_lying != -1)
@@ -1208,7 +1213,7 @@
var/canitem = !paralyzed && !stun && conscious && !chokehold && !restrained && has_arms
var/canitem = !paralyzed && !stun && stat_conscious && !chokehold && !restrained && has_arms
if(canitem)
mobility_flags |= (MOBILITY_USE | MOBILITY_PICKUP | MOBILITY_STORAGE)
else
@@ -1533,3 +1538,15 @@
/mob/living/proc/stop_look_up()
reset_perspective()
UnregisterSignal(src, COMSIG_MOVABLE_PRE_MOVE)
/mob/living/set_stat(new_stat)
. = ..()
if(isnull(.))
return
switch(.) //Previous stat.
if(UNCONSCIOUS)
cure_blind(UNCONSCIOUS_BLIND)
switch(stat) //Current stat.
if(UNCONSCIOUS)
become_blind(UNCONSCIOUS_BLIND)
@@ -23,6 +23,8 @@
var/cloneloss = 0 ///Damage caused by being cloned or ejected from the cloner early. slimes also deal cloneloss damage to victims
var/staminaloss = 0 ///Stamina damage, or exhaustion. You recover it slowly naturally, and are knocked down if it gets too high. Holodeck and hallucinations deal this.
var/crit_threshold = HEALTH_THRESHOLD_CRIT /// when the mob goes from "normal" to crit
///When the mob enters hard critical state and is fully incapacitated.
var/hardcrit_threshold = HEALTH_THRESHOLD_FULLCRIT
var/mobility_flags = MOBILITY_FLAGS_DEFAULT
+1 -1
View File
@@ -62,7 +62,7 @@
/mob/living/silicon/ai/updatehealth()
if(status_flags & GODMODE)
return
health = maxHealth - getOxyLoss() - getToxLoss() - getBruteLoss() - getFireLoss()
set_health(maxHealth - getOxyLoss() - getToxLoss() - getBruteLoss() - getFireLoss())
update_stat()
diag_hud_set_health()
disconnect_shell()
+1 -1
View File
@@ -281,7 +281,7 @@
/mob/living/silicon/pai/updatehealth()
if(status_flags & GODMODE)
return
health = maxHealth - getBruteLoss() - getFireLoss()
set_health(maxHealth - getBruteLoss() - getFireLoss())
update_stat()
/mob/living/silicon/pai/process()
+6 -12
View File
@@ -422,7 +422,7 @@
/mob/living/silicon/robot/update_icons()
cut_overlays()
icon_state = module.cyborg_base_icon
if(stat != DEAD && !(IsUnconscious() || IsStun() || IsParalyzed() || low_power_mode)) //Not dead, not stunned.
if(stat != DEAD && !(HAS_TRAIT(src, TRAIT_KNOCKEDOUT) || IsStun() || IsParalyzed() || low_power_mode)) //Not dead, not stunned.
if(!eye_lights)
eye_lights = new()
if(lamp_intensity > 2)
@@ -771,18 +771,12 @@
if(health <= -maxHealth) //die only once
death()
return
if(IsUnconscious() || IsStun() || IsKnockdown() || IsParalyzed() || getOxyLoss() > maxHealth*0.5)
if(stat == CONSCIOUS)
set_stat(UNCONSCIOUS)
become_blind(UNCONSCIOUS_BLIND)
update_mobility()
update_headlamp()
if(HAS_TRAIT(src, TRAIT_KNOCKEDOUT) || IsStun() || IsKnockdown() || IsParalyzed())
set_stat(UNCONSCIOUS)
else
if(stat == UNCONSCIOUS)
set_stat(CONSCIOUS)
cure_blind(UNCONSCIOUS_BLIND)
update_mobility()
update_headlamp()
set_stat(CONSCIOUS)
update_mobility()
update_headlamp()
diag_hud_set_status()
diag_hud_set_health()
diag_hud_set_aishell()
@@ -399,3 +399,25 @@ GLOBAL_LIST_INIT(blacklisted_borg_hats, typecacheof(list( //Hats that don't real
updatehealth()
if(prob(75) && Proj.damage > 0)
spark_system.start()
/mob/living/silicon/robot/adjustOxyLoss(amount, updating_health = TRUE, forced = FALSE)
. = ..()
if(isnull(.))
return
if(. <= (maxHealth * 0.5))
if(getOxyLoss() > (maxHealth * 0.5))
ADD_TRAIT(src, TRAIT_KNOCKEDOUT, OXYLOSS_TRAIT)
else if(getOxyLoss() <= (maxHealth * 0.5))
REMOVE_TRAIT(src, TRAIT_KNOCKEDOUT, OXYLOSS_TRAIT)
/mob/living/silicon/robot/setOxyLoss(amount, updating_health = TRUE, forced = FALSE)
. = ..()
if(isnull(.))
return
if(. <= (maxHealth * 0.5))
if(getOxyLoss() > (maxHealth * 0.5))
ADD_TRAIT(src, TRAIT_KNOCKEDOUT, OXYLOSS_TRAIT)
else if(getOxyLoss() <= (maxHealth * 0.5))
REMOVE_TRAIT(src, TRAIT_KNOCKEDOUT, OXYLOSS_TRAIT)
@@ -197,9 +197,6 @@
if(stat == DEAD)
. += "<span class='deadsay'>Upon closer examination, [p_they()] appear[p_s()] to be dead.</span>"
/mob/living/simple_animal/updatehealth()
..()
health = clamp(health, 0, maxHealth)
/mob/living/simple_animal/update_stat()
if(status_flags & GODMODE)
@@ -515,7 +512,7 @@
..()
/mob/living/simple_animal/update_mobility(value_otherwise = TRUE)
if(IsUnconscious() || IsParalyzed() || IsStun() || IsKnockdown() || IsParalyzed() || stat || resting)
if(HAS_TRAIT(src, TRAIT_KNOCKEDOUT) || IsParalyzed() || IsStun() || IsKnockdown() || IsParalyzed() || stat || resting)
drop_all_held_items()
mobility_flags = NONE
else if(buckled)
+2 -2
View File
@@ -445,7 +445,7 @@
REMOVE_TRAIT(src, TRAIT_DEATHCOMA, source)
if(stat != DEAD)
tod = null
update_stat()
/mob/living/proc/fakedeath(source, silent = FALSE)
if(stat == DEAD)
@@ -455,7 +455,7 @@
ADD_TRAIT(src, TRAIT_FAKEDEATH, source)
ADD_TRAIT(src, TRAIT_DEATHCOMA, source)
tod = station_time_timestamp()
update_stat()
///Unignores all slowdowns that lack the IGNORE_NOSLOW flag.
/mob/living/proc/unignore_slowdown(source)
@@ -91,9 +91,7 @@
/datum/nanite_program/protocol/offline/check_conditions()
var/is_offline = FALSE
if(nanites.host_mob.IsSleeping() || nanites.host_mob.IsUnconscious())
is_offline = TRUE
if(nanites.host_mob.stat == DEAD || HAS_TRAIT(nanites.host_mob, TRAIT_DEATHCOMA))
if(nanites.host_mob.stat >= UNCONSCIOUS) //DEAD or UNCONSCIOUS
is_offline = TRUE
if(nanites.host_mob.InCritical() && !HAS_TRAIT(nanites.host_mob, TRAIT_NOSOFTCRIT))
is_offline = TRUE
+1
View File
@@ -2130,6 +2130,7 @@
#include "code\modules\mob\living\death.dm"
#include "code\modules\mob\living\emote.dm"
#include "code\modules\mob\living\inhand_holder.dm"
#include "code\modules\mob\living\init_signals.dm"
#include "code\modules\mob\living\life.dm"
#include "code\modules\mob\living\living.dm"
#include "code\modules\mob\living\living_defense.dm"