Files
2016-03-06 20:52:14 +01:00

2122 lines
72 KiB
Plaintext

#define EVENT_ATTACHMENT_POINT_NONE 0
#define EVENT_ATTACHMENT_POINT_MELEE 1
#define EVENT_ATTACHMENT_POINT_ATTACKED 2
/proc/loadCustomCritterFromFile(var/target_name)
var/target
if (!isfile(target_name))
target = file(target_name)
else
target = target_name
if (!target)
return null
var/fname = "adventure/CRIT_LOAD_CUSTOMFILE"
if (fexists(fname))
fdel(fname)
var/savefile/F = new /savefile(fname)
F.dir.len = 0
F.eof = -1
boutput(F, null)
F.ImportText("/", file2text(target))
if (!F)
boutput(usr, "<span style=\"color:red\">Import failed.</span>")
else
var/datum/sandbox/S = new()
var/obj/critter/custom/template = new()
template.deserialize(F, "critter", S)
template.is_template = 1
if (fexists(fname))
fdel(fname)
return template
/obj/critter/custom
name = "custom critter"
desc = "custom critter"
icon_state = "floateye"
var/suspend_ai = 0
var/melee = 1
var/attack_power = 15
var/attack_type = "brute"
var/stun_prob = 20
var/anger_text = "%src% charges at %target%!"
var/chase_text = "%src% slams into %target%!"
var/stun_text = "%src% knocks down %target%!"
var/stun_fail_text = "%src% fails to knock down %target%!"
var/attack_text = "%src% bashes %target%!"
var/gib_corpses = 0
var/sound/anger_sound
var/sound/chase_sound
var/sound/stun_sound
var/sound/stun_fail_sound
var/sound/attack_sound
var/sound/death_sound
var/sound/ambient_sound
brutevuln = 1
firevuln = 1
var/explosivevuln = 1
var/datum/critterDeath/on_death = null
var/list/abil = list()
var/datum/critterLootTable/loot_table = null
var/dead_change_icon
var/icon/dead_icon
var/dead_icon_state
New()
..()
loot_table = new()
var/list/events = list()
ex_act(severity)
if (src.sleeping)
sleeping = 0
on_wake()
switch(severity)
if(1.0)
src.health -= 200 * explosivevuln
if (src.health <= 0)
src.CritterDeath()
return
if(2.0)
src.health -= 75 * explosivevuln
if (src.health <= 0)
src.CritterDeath()
return
else
src.health -= 25 * explosivevuln
if (src.health <= 0)
src.CritterDeath()
return
process()
if (suspend_ai)
return
..()
if (is_template || !alive)
return
if (prob(25))
play_optional_sound(ambient_sound)
for (var/datum/critterAbility/A in abil)
A.tick()
if (on_death)
on_death.tick()
attackby(obj/item/W as obj, mob/living/user as mob)
..()
if (W.force || istype(W, /obj/item/artifact/melee_weapon))
for (var/datum/critterEvent/E in events)
if (E.attachment_point == EVENT_ATTACHMENT_POINT_ATTACKED)
E.trigger()
seek_target()
src.anchored = initial(src.anchored)
if (src.target)
src.task = "chasing"
return
for (var/mob/living/C in hearers(src.seekrange,src))
if ((C.name == src.oldtarget_name) && (world.time < src.last_found + 100)) continue
if (iscarbon(C) && !src.atkcarbon) continue
if (istype(C, /mob/living/silicon/) && !src.atksilicon) continue
if (C.health < 0) continue
if (C in src.friends) continue
if (ishuman(C))
if (C:bioHolder && C:bioHolder.HasEffect("revenant"))
continue
if (C.name == src.attacker) src.attack = 1
if (iscarbon(C) && src.atkcarbon) src.attack = 1
if (istype(C, /mob/living/silicon/) && src.atksilicon) src.attack = 1
if (src.attack)
src.target = C
src.oldtarget_name = C.name
tokenized_message(anger_text, target)
play_optional_sound(anger_sound)
src.task = "chasing"
on_grump()
break
else
continue
ChaseAttack(mob/M)
if (!melee)
return
tokenized_message(chase_text, target)
play_optional_sound(chase_sound)
if (stun_prob)
spawn(10)
if (get_dist(src, target) <= 1)
if (prob(stun_prob))
M.stunned += 3
tokenized_message(stun_text, target)
play_optional_sound(stun_sound)
else
tokenized_message(stun_fail_text, target)
play_optional_sound(stun_fail_sound)
proc/dodamage(var/mob/M, var/atype, var/damage)
switch (atype)
if ("brute")
random_brute_damage(src.target, damage)
if ("burn")
random_burn_damage(src.target, damage)
if ("toxin")
M.take_toxin_damage(damage)
M.updatehealth()
if ("suffocation")
M.take_oxygen_deprivation(damage)
M.updatehealth()
if ("radiation")
M.radiation += damage
if (damage != 0)
M.take_toxin_damage(damage / 2)
M.updatehealth()
CritterAttack(mob/N)
if (!melee)
return
src.attacking = 1
tokenized_message(attack_text, target)
play_optional_sound(attack_sound)
var/damage = max(rand(attack_power), rand(attack_power))
var/mob/M = target
dodamage(M, attack_type, damage)
for (var/datum/critterEvent/E in events)
if (E.attachment_point == EVENT_ATTACHMENT_POINT_MELEE)
E.trigger()
spawn(25)
src.attacking = 0
CritterDeath()
if (!src.alive)
return
//src.icon_state += "-dead"
src.alive = 0
src.anchored = 0
src.density = 0
loot_table.drop()
if (dead_change_icon)
icon = dead_icon
icon_state = dead_icon_state
tokenized_message(death_text, null)
play_optional_sound(death_sound)
if (on_death)
on_death.doOnDeath()
walk_to(src,0)
clone()
var/obj/critter/custom/C = ..()
C.melee = melee
C.attack_power = attack_power
C.attack_type = attack_type
C.stun_prob = stun_prob
C.anger_text = anger_text
C.chase_text = chase_text
C.stun_text = stun_text
C.stun_fail_text = stun_fail_text
C.attack_text = attack_text
C.gib_corpses = gib_corpses
C.death_text = death_text
C.explosivevuln = explosivevuln
C.dead_icon = dead_icon
C.dead_icon_state = dead_icon_state
C.dead_change_icon = dead_change_icon
C.anger_sound = anger_sound
C.chase_sound = chase_sound
C.stun_sound = stun_sound
C.stun_fail_sound = stun_fail_sound
C.attack_sound = attack_sound
C.death_sound = death_sound
C.ambient_sound = ambient_sound
C.loot_table = loot_table.clone()
C.loot_table.C = C
if (on_death)
C.on_death = on_death.clone()
C.on_death.C = C
C.abil.len = abil.len
for (var/i = 1, i <= abil.len, i++)
var/datum/critterAbility/A = abil[i]
if (istype(A))
var/datum/critterAbility/B = A.clone()
C.abil[i] = B
B.attach(C)
B.C = C
return C
serialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
..()
F["[path].melee"] << melee
F["[path].attack_power"] << attack_power
F["[path].attack_type"] << attack_type
F["[path].stun_prob"] << stun_prob
F["[path].anger_text"] << anger_text
F["[path].chase_text"] << chase_text
F["[path].stun_text"] << stun_text
F["[path].stun_fail_text"] << stun_fail_text
F["[path].attack_text"] << attack_text
F["[path].gib_corpses"] << gib_corpses
F["[path].death_text"] << death_text
F["[path].anger_sound"] << anger_sound
F["[path].chase_sound"] << chase_sound
F["[path].stun_sound"] << stun_sound
F["[path].stun_fail_sound"] << stun_fail_sound
F["[path].attack_sound"] << attack_sound
F["[path].death_sound"] << death_sound
F["[path].ambient_sound"] << ambient_sound
F["[path].explosivevuln"] << explosivevuln
F["[path].dead_change_icon"] << dead_change_icon
icon_serializer(F, "[path].dead_icon", sandbox, dead_icon, dead_icon_state)
loot_table.serialize(F, "[path].loot_table", sandbox)
F["[path].abil.LEN"] << abil.len
if (on_death)
F["[path].on_death.type"] << on_death.type
on_death.serialize(F, "[path].on_death", sandbox)
for (var/i = 1, i <= abil.len, i++)
var/datum/critterAbility/A = abil[i]
if (istype(A))
F["[path].abil.[i].type"] << A.type
A.serialize(F, "[path].abil.[i]", sandbox)
deserialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
..()
F["[path].melee"] >> melee
F["[path].attack_power"] >> attack_power
F["[path].attack_type"] >> attack_type
F["[path].stun_prob"] >> stun_prob
F["[path].anger_text"] >> anger_text
F["[path].chase_text"] >> chase_text
F["[path].stun_text"] >> stun_text
F["[path].stun_fail_text"] >> stun_fail_text
F["[path].attack_text"] >> attack_text
F["[path].gib_corpses"] >> gib_corpses
F["[path].death_text"] >> death_text
F["[path].explosivevuln"] >> explosivevuln
F["[path].anger_sound"] >> anger_sound
F["[path].chase_sound"] >> chase_sound
F["[path].stun_sound"] >> stun_sound
F["[path].stun_fail_sound"] >> stun_fail_sound
F["[path].attack_sound"] >> attack_sound
F["[path].death_sound"] >> death_sound
F["[path].ambient_sound"] >> ambient_sound
F["[path].dead_change_icon"] >> dead_change_icon
var/datum/iconDeserializerData/IDS = icon_deserializer(F, "[path].dead_icon", sandbox, dead_icon, dead_icon_state)
dead_icon = IDS.icon
dead_icon_state = IDS.icon_state
loot_table = new()
loot_table.deserialize(F, "[path].loot_table", sandbox)
loot_table.C = src
var/odt
F["[path].on_death.type"] >> odt
if (odt)
on_death = new odt()
on_death.deserialize(F, "[path].on_death", sandbox)
on_death.C = src
var/abs
F["[path].abil.LEN"] >> abs
abil.len = abs
for (var/i = 1, i <= abil.len, i++)
var/T
F["[path].abil.[i].type"] >> T
if (T)
var/datum/critterAbility/A = new T()
if (istype(A))
A.deserialize(F, "[path].abil.[i]", sandbox)
A.attach(src)
abil[i] = A
else
logTheThing("debug", null, null, "<b>Marquesas/CritterCreator:</b> Cannot deserialize type [T].")
proc/play_optional_sound(var/sound/sound)
if (sound)
playsound(src, sound, 50, 1)
proc/addUntiedEvent(var/datum/critterEvent/E)
events += E
E.attachment_point = EVENT_ATTACHMENT_POINT_NONE
proc/addAttackEvent(var/datum/critterEvent/E)
events += E
E.attachment_point = EVENT_ATTACHMENT_POINT_MELEE
/datum/critterCreatorHolder
var/list/critterCreators = list()
var/list/activeCritterTypes = list()
proc/blank(var/mob/M)
if (!M.client)
boutput(M, "<span style=\"color:red\">Hello.</span>")
return 0
// look I think it's okay if you maybe let non-admins access this sometimes
/*if (!M.client.holder)
boutput(M, "<span style=\"color:red\">What are you doing here?</span>")
return 0
if (M.client.holder.level < LEVEL_PA)
boutput(M, "<span style=\"color:red\">You must be at least PA to use this.</span>")
return 0*/
var/key = M.ckey
if (!(key in critterCreators))
critterCreators += key
critterCreators[key] = new /datum/critterCreator
proc/getCreator(var/mob/M)
var/key = M.ckey
if (!(key in critterCreators))
if (!blank(M))
return null
return critterCreators[key]
var/global/datum/critterCreatorHolder/critter_creator_controller = new()
/datum/critterLoot
var/datum/critterLootTable/lootTable
var/dropped = null
var/amount = 1
var/chance = 100
proc/configuration(var/datum/critterCreator/configurer)
. = configurer.clickable_link("lootamount", amount, "0", "\ref[src]")
. += " "
. += configurer.clickable_link("lootdropped", configurer.stripPath(dropped), "(null)", "\ref[src]")
. += " ("
. += configurer.clickable_link("lootchance", "[chance]%", "0%", "\ref[src]")
. += ") "
. += configurer.clickable_link("lootremove", "(remove)", "(remove)", "\ref[src]")
proc/change_configuration(var/datum/critterCreator/configurer, var/which)
switch (which)
if ("amount")
amount = configurer.getNum("dropped amount", amount)
if ("dropped")
var/filter = configurer.getText("enter part of the pathname of the dropped object", "")
var/typename = get_one_match(filter, /obj/item)
if (typename)
dropped = typename
if ("chance")
chance = configurer.getNum("drop chance", chance)
if ("remove")
lootTable.loot -= src
qdel(src)
proc/clone()
var/datum/critterLoot/L = new type()
L.dropped = dropped
L.amount = amount
L.chance = chance
return L
proc/serialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
F["[path].dropped"] << dropped
F["[path].amount"] << amount
F["[path].chance"] << chance
proc/deserialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
F["[path].dropped"] >> dropped
F["[path].amount"] >> amount
F["[path].chance"] >> chance
proc/drop(var/max = -1)
if (!dropped)
return 0
if (amount < 1)
return 0
if (prob(chance))
for (var/i = 1, i <= amount && (i <= max || max == -1), i++)
new dropped(get_turf(lootTable.C))
return amount
return 0
/datum/critterLootTable
var/maxDropped = 2
var/list/loot = list()
var/obj/critter/custom/C
proc/configuration(var/datum/critterCreator/configurer)
. = "<strong>Loot table dropping up to "
. += configurer.clickable_link("loottable", "[maxDropped] [maxDropped == 1 ? "item" : "items"]", "0 items", "maxDropped")
. += ":</strong><br/><ul>"
for (var/i = 1, i <= loot.len, i++)
var/datum/critterLoot/L = loot[i]
. += "<li>"
. += L.configuration(configurer)
. += "</li>"
. += "<li>"
. += configurer.clickable_link("loottable", "(add new)", "(add new)", "addNew")
. += "</ul>"
proc/change_configuration(var/datum/critterCreator/configurer, var/which)
switch (which)
if ("maxDropped")
maxDropped = configurer.getNum("maximum dropped amount", maxDropped)
if ("addNew")
var/datum/critterLoot/L = new()
L.lootTable = src
loot += L
proc/clone()
var/datum/critterLootTable/LT = new type()
LT.maxDropped = maxDropped
LT.loot.len = loot.len
for (var/i = 1, i <= loot.len, i++)
var/datum/critterLoot/L = loot[i]
var/datum/critterLoot/L2 = L.clone()
L2.lootTable = LT
LT.loot[i] = L2
return LT
proc/serialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
F["[path].maxDropped"] << maxDropped
F["[path].loot.LEN"] << loot.len
for (var/i = 1, i <= loot.len, i++)
var/datum/critterLoot/L = loot[i]
L.serialize(F, "[path].loot.[i]", sandbox)
proc/deserialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
F["[path].maxDropped"] >> maxDropped
var/lootc
F["[path].loot.LEN"] >> lootc
loot.len = lootc
for (var/i = 1, i <= loot.len, i++)
var/datum/critterLoot/L = new()
L.deserialize(F, "[path].loot.[i]", sandbox)
loot[i] = L
L.lootTable = src
proc/drop()
var/CD = maxDropped
for (var/datum/critterLoot/L in loot)
CD -= L.drop(CD)
if (CD <= 0)
return
/datum/critterDeath
var/name = "do nothing"
var/obj/critter/custom/C
proc/configuration(var/datum/critterCreator/configurer)
return ""
proc/change_configuration(var/datum/critterCreator/configurer, var/which)
return
proc/tick()
return
proc/clone()
return new type()
proc/serialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
return
proc/deserialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
return
proc/doOnDeath()
return
/datum/critterDeath/gib
name = "gib"
var/gibtype = 1
configuration(var/datum/critterCreator/configurer)
. = "<span class='attribute-name'>Gib type: </span>"
. += configurer.clickable_link("deathconf", gibtype ? "organic" : "machine", "machine", "gibtype")
change_configuration(var/datum/critterCreator/configurer, var/which)
if (which == "gibtype")
gibtype = !gibtype
clone()
var/datum/critterDeath/gib/D = ..()
D.gibtype = gibtype
return D
serialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
F["[path].gibtype"] << gibtype
deserialize(var/savefile/F, path, var/datum/sandbox/sandbox)
F["[path].gibtype"] >> gibtype
doOnDeath()
if (gibtype)
gibs(C.loc, list())
else
robogibs(C.loc, list())
C.loc = null
qdel(C)
/datum/critterDeath/explode
name = "explode"
var/power = 5
var/delay = 20
configuration(var/datum/critterCreator/configurer)
. = "<span class='attribute-name'>Creates an explosion of power </span>"
. += configurer.clickable_link("deathconf", power, "0", "power")
. += "<span class='attribute-name'> after a delay of </span>"
. += configurer.clickable_link("deathconf", delay / 10, "0", "delay")
. += " seconds."
change_configuration(var/datum/critterCreator/configurer, var/which)
switch(which)
if ("power")
power = configurer.getNum("explosion power", power)
if ("delay")
delay = configurer.getNum("explosion delay in 1/10th of seconds", delay)
clone()
var/datum/critterDeath/explode/D = ..()
D.power = power
D.delay = delay
return D
serialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
F["[path].power"] << power
F["[path].delay"] << delay
deserialize(var/savefile/F, path, var/datum/sandbox/sandbox)
F["[path].power"] >> power
F["[path].delay"] >> delay
doOnDeath()
var/L = C.loc
spawn (delay)
explosion_new(C, L, power)
C.loc = null
qdel(C)
/datum/critterDeath/smoke
name = "vaporize into smoke"
var/reagent = "water"
var/delay = 20
configuration(var/datum/critterCreator/configurer)
. = "<span class='attribute-name'>Becomes a puff of </span>"
. += configurer.clickable_link("deathconf", reagent, "0", "reagent")
. += "<span class='attribute-name'> after a delay of </span>"
. += configurer.clickable_link("deathconf", delay / 10, "0", "delay")
. += " seconds."
change_configuration(var/datum/critterCreator/configurer, var/which)
switch(which)
if ("reagent")
reagent = configurer.getText("smoked reagent", reagent)
if ("delay")
delay = configurer.getNum("explosion delay in 1/10th of seconds", delay)
clone()
var/datum/critterDeath/smoke/D = ..()
D.reagent = reagent
D.delay = delay
return D
serialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
F["[path].reagent"] << reagent
F["[path].delay"] << delay
deserialize(var/savefile/F, path, var/datum/sandbox/sandbox)
F["[path].reagent"] >> reagent
F["[path].delay"] >> delay
doOnDeath()
var/L = C.loc
spawn (delay)
var/datum/reagents/holder = new()
holder.my_atom = C
holder.add_reagent(reagent, 50)
smoke_reaction(holder, 4, L)
C.loc = null
qdel(C)
/datum/critterEvent
var/name = "never"
var/datum/critterAbility/attached
var/abstract = 0
var/configured = 0
var/obj/critter/custom/C = null
var/attachment_point = EVENT_ATTACHMENT_POINT_NONE
proc/onAttach(var/obj/critter/custom/CR)
C = CR
C.addUntiedEvent(src)
proc/varChanged(var/varname, var/oldvalue, var/newvalue)
return
proc/tick()
return
proc/change_configuration(var/datum/critterCreator/configurer, var/which)
return
proc/trigger()
attached.use()
proc/configuration(var/datum/critterCreator/configurer)
return ""
proc/clone()
return new type()
proc/serialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
return
proc/deserialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
return
/datum/critterEvent/always
name = "when available"
tick()
..()
trigger()
/datum/critterEvent/mobsInView
name = "when there are living mobs in view"
configured = 1
abstract = 0
var/atLeast = 1
tick()
..()
var/count = 0
for (var/mob/M in view(7, C))
if (istype(M, /mob/living))
if (M.stat != 2)
count++
if (count >= atLeast)
trigger()
configuration(var/datum/critterCreator/configurer)
return "at least " + configurer.clickable_link("abilevent", atLeast, "0", "atLeast")
change_configuration(var/datum/critterCreator/configurer, var/which)
if (which == "atLeast")
atLeast = configurer.getNum("mob count", atLeast)
clone()
var/datum/critterEvent/mobsInView/E = ..()
E.atLeast = atLeast
return E
serialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
..()
F["[path].atLeast"] << atLeast
deserialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
..()
F["[path].atLeast"] >> atLeast
/datum/critterEvent/melee
name = "when melee attacking"
onAttach(var/obj/critter/custom/CR)
C = CR
C.addAttackEvent(src)
/datum/critterEvent/varCrossed
var/matchVar = null // the variable to listen to, for example health
var/crossDirection = -1 // -1 : when crossing downwards, 1: when crossing upwards, 0: any direction
var/threshold = 50 // the threshold the variable needs to cross in the chosen direction to trigger
var/is_percentage = 1 // if 1, threshold is interpreted as a percentage value instead of an absolute one
abstract = 1
var/last_value = 0
var/RT
// 100% is the value of the variable at the time of attachment
onAttach(var/obj/critter/custom/CR)
if (!(matchVar in CR.vars))
CRASH("Cannot attach event to [CR]: [matchVar] does not exist.")
..()
if (matchVar && is_percentage)
var/mult = threshold / 100.0
RT = C.vars[matchVar] * mult
last_value = C.vars[matchVar]
else
RT = threshold
varChanged(var/varname, var/oldvalue, var/newvalue)
if (varname == matchVar)
if (crossDirection >= 0 && oldvalue < RT && RT < newvalue)
trigger()
else if (crossDirection <= 0 && oldvalue > RT && RT > newvalue)
trigger()
/datum/critterEvent/varCrossed/health
name = "when health drops below percentage"
abstract = 0
matchVar = "health"
crossDirection = -1
threshold = 0
is_percentage = 1
configured = 1
configuration(var/datum/critterCreator/configurer)
return configurer.clickable_link("abilevent", threshold, "0", "threshold") + "%"
change_configuration(var/datum/critterCreator/configurer, var/which)
if (which == "threshold")
threshold = configurer.getNum("threshold percentage", threshold)
clone()
var/datum/critterEvent/varCrossed/health/E = ..()
E.threshold = threshold
return E
serialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
..()
F["[path].threshold"] << threshold
deserialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
..()
F["[path].threshold"] >> threshold
/datum/critterEvent/varIs
var/matchVar = null // the variable to listen to, for example health
var/direction = -1 // -1 : below threshold, 1: greater than threshold, 0: equals threshold
var/threshold = 50 // the threshold the variable needs to relate to in the chosen direction to trigger
var/is_percentage = 1 // if 1, threshold is interpreted as a percentage value instead of an absolute one
var/real_threshold
abstract = 1
// 100% is the value of the variable at the time of attachment
onAttach(var/obj/critter/custom/CR)
if (!(matchVar in CR.vars))
CRASH("Cannot attach event to [CR]: [matchVar] does not exist.")
..()
if (matchVar && is_percentage)
var/mult = threshold / 100.0
real_threshold = C.vars[matchVar] * mult
else if (matchVar)
real_threshold = threshold
tick()
var/newvalue = C.vars[matchVar]
if (direction > 0 && newvalue > real_threshold)
trigger()
else if (direction < 0 && newvalue < real_threshold)
trigger()
else if (direction == 0 && newvalue == real_threshold)
trigger()
/datum/critterEvent/varRange
var/matchVar = null // the variable to listen to, for example health
var/minimum = 20 // the variable must be greater than this
var/maximum = 50 // the variable must be less than this
var/is_percentage = 1 // if 1, threshold is interpreted as a percentage value instead of an absolute one
var/RM
var/RX
abstract = 1
// 100% is the value of the variable at the time of attachment
onAttach(var/obj/critter/custom/CR)
if (!(matchVar in CR.vars))
CRASH("Cannot attach event to [CR]: [matchVar] does not exist.")
..()
if (matchVar && is_percentage)
var/mult = minimum / 100.0
RM = C.vars[matchVar] * mult
mult = maximum / 100.0
RX = C.vars[matchVar] * mult
else
RM = minimum
RX = maximum
tick()
var/newvalue = C.vars[matchVar]
if (RM <= newvalue && newvalue < RX)
trigger()
/datum/critterEvent/varRange/health
name = "when health is within percentage range"
configured = 1
change_configuration(var/datum/critterCreator/configurer, var/which)
if (which == "minimum")
minimum = configurer.getNum("minimum percentage", minimum)
else if (which == "maximum")
maximum = configurer.getNum("minimum percentage", maximum)
configuration(var/datum/critterCreator/configurer)
return configurer.clickable_link("abilevent", minimum, "0", "minimum") + "% - " + configurer.clickable_link("abilevent", maximum, "0", "maximum") + "%"
clone()
var/datum/critterEvent/varRange/health/E = ..()
E.minimum = minimum
E.maximum = maximum
return E
serialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
..()
F["[path].minimum"] << minimum
F["[path].maximum"] << maximum
deserialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
..()
F["[path].minimum"] >> minimum
F["[path].maximum"] >> maximum
/datum/critterEvent/varIs/healthLow
name = "when health is below percentage"
abstract = 0
matchVar = "health"
direction = -1
threshold = 0
is_percentage = 1
configured = 1
change_configuration(var/datum/critterCreator/configurer, var/which)
if (which == "threshold")
threshold = configurer.getNum("threshold percentage", threshold)
configuration(var/datum/critterCreator/configurer)
return configurer.clickable_link("abilevent", threshold, "0", "threshold") + "%"
clone() // boy i sure do miss implicit copy constructors
var/datum/critterEvent/varIs/healthLow/E = ..()
E.threshold = threshold
return E
serialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
..()
F["[path].threshold"] << threshold
deserialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
..()
F["[path].threshold"] >> threshold
/datum/critterEvent/varIs/healthHigh
name = "when health is above percentage"
abstract = 0
matchVar = "health"
direction = -1
threshold = 0
is_percentage = 1
configured = 1
change_configuration(var/datum/critterCreator/configurer, var/which)
if (which == "threshold")
threshold = configurer.getNum("threshold percentage", threshold)
configuration(var/datum/critterCreator/configurer)
return configurer.clickable_link("abilevent", threshold, "0", "threshold") + "%"
clone()
var/datum/critterEvent/varIs/healthHigh/E = ..()
E.threshold = threshold
return E
serialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
..()
F["[path].threshold"] << threshold
deserialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
..()
F["[path].threshold"] >> threshold
/datum/critterAbility
var/name = "Ability"
var/datum/critterEvent/event
var/chance = 50
var/cooldown = 20
var/next_usable = 0
var/abstract = 1
var/obj/critter/custom/C
var/static/list/events_cache = list()
proc/serialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
F["[path].chance"] << chance
F["[path].cooldown"] << cooldown
if (event)
F["[path].event.type"] << event.type
event.serialize(F, "[path].event", sandbox)
proc/deserialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
F["[path].chance"] >> chance
F["[path].cooldown"] >> cooldown
var/ET
F["[path].event.type"] >> ET
if (ET)
event = new ET()
event.deserialize(F, "[path].event", sandbox)
event.attached = src
else
logTheThing("debug", "usr", null, "<b>Marquesas/CritterCreator: </b> Failed to deserialize event for ability.")
proc/attach(var/obj/critter/custom/CR)
C = CR
if (event)
event.onAttach(C)
proc/tick()
if (event)
event.tick()
proc/clone()
var/datum/critterAbility/copy = new type()
copy.chance = chance
copy.cooldown = cooldown
if (event)
copy.event = event.clone()
copy.event.attached = copy
return copy
proc/use()
if (check_if_usable())
if (use_ability())
put_on_cooldown()
proc/check_if_usable()
if (!prob(chance))
return 0
if (next_usable > world.time)
return 0
return 1
proc/put_on_cooldown()
next_usable = world.time + cooldown
proc/use_ability()
return 0
proc/build_events_cache()
for (var/evtype in typesof(/datum/critterEvent))
var/datum/critterEvent/ev = new evtype()
if (ev.abstract)
qdel(ev)
else
events_cache += ev
proc/change_configuration(var/datum/critterCreator/configurer, var/which)
switch(which)
if ("event")
if (!events_cache.len)
build_events_cache()
var/datum/critterEvent/ev = input("Which event?", "Event", null) in events_cache
if (event)
if (ev.type == event.type)
return
qdel(event)
event = ev.clone()
event.attached = src
if ("chance")
chance = configurer.getNum("ability usage chance", chance)
if ("cooldown")
cooldown = configurer.getNum("ability cooldown in 1/10th of seconds", cooldown)
proc/configuration(var/datum/critterCreator/configurer)
var/output = "<span class='attribute-name'>Ability: </span>"
output += configurer.clickable_link("abilchange", src, "none")
output += "<br><span class='attribute-name'>Use this ability </span>"
output += configurer.clickable_link("abilconf", event, "never", "event")
if (event)
if (event.configured)
output += " ([event.configuration(configurer)])"
output += "<br><span class='attribute-name'>with a probability of </span>"
output += configurer.clickable_link("abilconf", chance, "0", "chance")
output += "%<br><span class='attribute-name'>and cooldown of </span>"
output += configurer.clickable_link("abilconf", cooldown / 10, "0", "cooldown")
output += " seconds.<br>"
return output
/datum/critterAbility/criticalStrike
name = "critical strike"
var/bonus_damage = 5
var/critical_text = "%src% critically hits %target%!"
var/sound/critical_sound
var/melee = 1
abstract = 0
serialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
..()
F["[path].bonus_damage"] << bonus_damage
F["[path].critical_text"] << critical_text
F["[path].melee"] << melee
F["[path].critical_sound"] << critical_sound
deserialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
..()
F["[path].bonus_damage"] >> bonus_damage
F["[path].critical_text"] >> critical_text
F["[path].melee"] >> melee
F["[path].critical_sound"] >> critical_sound
use_ability()
if (C.target && ismob(C.target))
var/mob/M = C.target
if (melee && get_dist(C, M) > C.attack_range)
return 0
C.tokenized_message(critical_text, M)
C.play_optional_sound(critical_sound)
M.TakeDamage("chest", bonus_damage, 0)
return 1
else
logTheThing("debug", null, null, "<b>Marquesas/CritterCreator: </b> Cannot reagent inject target, target is [C.target].")
return 0
change_configuration(var/datum/critterCreator/configurer, var/which)
..()
switch(which)
if ("critical_text")
critical_text = configurer.getText("critical text", critical_text)
if ("bonus_damage")
bonus_damage = configurer.getNum("bonus damage", bonus_damage)
if ("melee")
melee = !melee
configurer.sound_router(list("abilconf" = which), "abilconf", "critical_sound", src, "critical_sound")
configuration(var/datum/critterCreator/configurer)
. = ..()
. += "<span class='attribute-name'>The critical strike will deal </span>"
. += configurer.clickable_link("abilconf", bonus_damage, "0", "bonus_damage")
. += " extra damage.<br/><span class='attribute-name'> The following text will be displayed: </span><br>"
. += configurer.clickable_link("abilconf", critical_text, "(null)", "critical_text")
. += "<br/>"
. += configurer.sound_link("Critical hit", critical_sound, "abilconf", "critical_sound")
. += "<br/>This ability works at [configurer.clickable_link("abilconf", melee ? "melee" : "any", "any", "melee")] range.<br>"
clone()
var/datum/critterAbility/criticalStrike/A = ..()
A.bonus_damage = bonus_damage
A.critical_text = critical_text
A.critical_sound = critical_sound
A.melee = melee
return A
/datum/critterAbility/reagent
name = "reagent inject"
abstract = 0
var/reagent_id = "mercury"
var/inject_amount = 5
var/inject_text = "%src% stabs %target% with its stinger!"
var/sound/inject_sound
var/melee = 1
serialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
..()
F["[path].reagent_id"] << reagent_id
F["[path].inject_amount"] << inject_amount
F["[path].inject_text"] << inject_text
F["[path].melee"] << melee
F["[path].inject_sound"] << inject_sound
deserialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
..()
F["[path].reagent_id"] >> reagent_id
F["[path].inject_amount"] >> inject_amount
F["[path].inject_text"] >> inject_text
F["[path].melee"] >> melee
F["[path].inject_sound"] >> inject_sound
use_ability()
if (!C)
logTheThing("debug", null, null, "<b>Marquesas/CritterCreator: </b> Error using ability \ref[src] ([type]). C: \ref[C] [C].")
return 0
if (C.target && ismob(C.target))
var/mob/M = C.target
if (melee && get_dist(C, M) > C.attack_range)
return 0
C.tokenized_message(inject_text, M)
C.play_optional_sound(inject_sound)
M.reagents.add_reagent(reagent_id, inject_amount)
return 1
else
logTheThing("debug", null, null, "<b>Marquesas/CritterCreator: </b> Cannot reagent inject target, target is [C.target].")
return 0
change_configuration(var/datum/critterCreator/configurer, var/which)
..()
switch(which)
if ("inject_text")
inject_text = configurer.getText("inject text", inject_text)
if ("inject_amount")
inject_amount = configurer.getNum("injected amount", inject_amount)
if ("reagent_id")
reagent_id = configurer.getText("reagent id", reagent_id)
if ("melee")
melee = !melee
configurer.sound_router(list("abilconf" = which), "abilconf", "inject_sound", src, "inject_sound")
configuration(var/datum/critterCreator/configurer)
. = ..()
. += "<span class='attribute-name'>The critter will inject </span>"
. += configurer.clickable_link("abilconf", inject_amount, "0", "inject_amount")
. += " [inject_amount == 1 ? "unit" : "units"] of "
. += configurer.clickable_link("abilconf", reagent_id, "0", "reagent_id")
. += ".<br/><span class='attribute-name'> The following text will be displayed: </span><br>"
. += configurer.clickable_link("abilconf", inject_text, "(null)", "inject_text")
. += "<br>"
. += configurer.sound_link("Injection", inject_sound, "abilconf", "inject_sound")
. += "<br/>This ability works at [configurer.clickable_link("abilconf", melee ? "melee" : "any", "any", "melee")] range.<br>"
clone()
var/datum/critterAbility/reagent/A = ..()
A.reagent_id = reagent_id
A.inject_text = inject_text
A.inject_sound = inject_sound
A.inject_amount = inject_amount
A.melee = melee
return A
/datum/critterAbility/projectile
name = "shoot projectile"
var/projectile_type = /datum/projectile/laser
var/datum/projectile/current_projectile = null
var/fire_text = "%src% shoots at %target%!"
abstract = 0
serialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
..()
F["[path].projectile_type"] << projectile_type
F["[path].fire_text"] << fire_text
deserialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
..()
F["[path].projectile_type"] >> projectile_type
F["[path].fire_text"] >> fire_text
current_projectile = new projectile_type()
proc/fire_at(var/turf/T, var/mob/RT)
var/turf/S = get_turf(C)
shoot_projectile_ST(S, current_projectile, T)
C.tokenized_message(fire_text, RT)
return 1
use_ability()
if (!current_projectile)
current_projectile = new projectile_type()
if (!current_projectile)
return 0
var/turf/T
var/RT = null
if (C.target)
T = get_turf(C.target)
RT = C.target
if (!T)
var/mob/M = locate(/mob/living) in view(6, C)
if (M)
T = get_turf(M)
RT = M
if (!T)
return 0
return fire_at(T, RT)
change_configuration(var/datum/critterCreator/configurer, var/which)
..()
switch(which)
if ("projectile_type")
projectile_type = configurer.getTypeExclusive("projectile type", projectile_type, /datum/projectile)
if ("fire_text")
fire_text = configurer.getText("firing text", fire_text)
configuration(var/datum/critterCreator/configurer)
. = ..()
. += "The critter fire "
. += configurer.clickable_link("abilconf", configurer.stripPath(projectile_type), "(null)", "projectile_type")
. += " projectiles.<br>"
. += ".<br/><span class='attribute-name'> The following text will be displayed: </span><br>"
. += configurer.clickable_link("abilconf", fire_text, "(null)", "fire_text")
. += "<br>"
clone()
var/datum/critterAbility/projectile/A = ..()
A.projectile_type = projectile_type
A.fire_text = fire_text
return A
/datum/critterAbility/projectile/burst
name = "projectile burst"
fire_text = "%src% launches a barrage of projectiles!"
var/turn_angle = 10
var/count = 4
var/curr_angle = 0
serialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
..()
F["[path].turn_angle"] << turn_angle
F["[path].count"] << count
deserialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
..()
F["[path].turn_angle"] >> turn_angle
F["[path].count"] >> count
proc/fire_in_direction(var/angle)
var/turf/S = get_turf(C)
shoot_projectile_XY(S, current_projectile, cos(angle), sin(angle))
return 1
use_ability()
if (!current_projectile)
current_projectile = new projectile_type()
if (!current_projectile)
return 0
var/curr = curr_angle
var/step_angle = 360.0 / count
if(current_projectile.shot_sound)
if (narrator_mode)
playsound(C, 'sound/vox/shoot.ogg', 50)
else
playsound(C, current_projectile.shot_sound, 50)
for (var/i = 1, i <= count, i++)
fire_in_direction(curr)
curr += step_angle
C.tokenized_message(fire_text, null)
curr_angle += turn_angle
while (curr_angle >= 360)
curr_angle -= 360
while (curr_angle < 0)
curr_angle += 360
return 1
change_configuration(var/datum/critterCreator/configurer, var/which)
..()
switch (which)
if ("turn_angle")
turn_angle = configurer.getNum("turn angle", turn_angle)
if ("count")
count = configurer.getNum("count", count)
configuration(var/datum/critterCreator/configurer)
. = ..()
. += "<br>Bursts "
. += configurer.clickable_link("abilconf", count, "0", "count")
. += " projectiles uniformly spread across a circle around the critter.<br>"
. += "The first projectile is fired towards the right. The position of 'right' is rotated by "
. += configurer.clickable_link("abilconf", "[turn_angle]&ordm;", "0&ordm;", "turn_angle")
. += " after firing.<br>"
clone()
var/datum/critterAbility/projectile/burst/A = ..()
A.turn_angle = turn_angle
A.count = count
A.fire_text = fire_text
return A
/datum/critterAbility/frenzy
name = "frenzy"
abstract = 0
var/frenzy_text = "%src% goes into frenzy!"
var/sound/frenzy_sound
var/sound/frenzy_attack_sound
var/frenzy_attack = "%src% rips a chunk off %target%!"
var/frenzy_duration = 30
var/attack_power = 4
var/attacktype = "brute"
var/attack_cooldown = 3
var/stunlocks = 0
var/frenzying = 0
use_ability()
if (frenzying)
return 0
var/mob/atmob = C.target
if (!istype(atmob))
return 0
frenzying = 1
C.tokenized_message(frenzy_text)
C.play_optional_sound(frenzy_sound)
C.suspend_ai = 1
spawn(frenzy_duration)
frenzying = 0
C.suspend_ai = 0
spawn(0)
while(frenzying)
var/turf/T = get_turf(atmob)
if (!T)
return
C.set_loc(T)
C.dir = pick(1,2,4,8)
C.tokenized_message(frenzy_attack, atmob)
C.play_optional_sound(frenzy_attack_sound)
C.dodamage(atmob, attacktype, max(rand(attack_power), rand(attack_power)))
if (stunlocks)
atmob.weakened += attack_cooldown / 3 * 2
sleep(attack_cooldown)
return 1
change_configuration(var/datum/critterCreator/configurer, var/which)
..()
switch (which)
if ("frenzy_duration")
frenzy_duration = configurer.getNum("frenzy duration in 1/10ths of seconds", frenzy_duration)
if ("attack_cooldown")
attack_cooldown = configurer.getNum("attack cooldown in 1/10ths of seconds", attack_cooldown)
if ("attack_power")
attack_power = configurer.getNum("attack power", attack_power)
if ("attacktype")
attacktype = configurer.getEnum("attack type", attacktype, list("brute", "burn", "toxin", "suffocation", "radiation"))
if ("stunlocks")
stunlocks = !stunlocks
if ("frenzy_text")
frenzy_text = configurer.getText("frenzy text", frenzy_text)
if ("frenzy_attack")
frenzy_attack = configurer.getText("frenzy_attack", frenzy_attack)
configurer.sound_router(list("abilconf" = which), "abilconf", "frenzy_sound", src, "frenzy_sound")
configurer.sound_router(list("abilconf" = which), "abilconf", "frenzy_attack_sound", src, "frenzy_attack_sound")
configuration(var/datum/critterCreator/configurer)
. = ..()
. += "<b>Goes into a murderous frenzy for </b>"
. += configurer.clickable_link("abilconf", frenzy_duration / 10, "0", "frenzy_duration")
. += " seconds,<br><b>attacking every </b>"
. += configurer.clickable_link("abilconf", attack_cooldown / 10, "0", "attack_cooldown")
. += " seconds<br><b>with an attack power of </b>"
. += configurer.clickable_link("abilconf", attack_power, "0", "attack_power")
. += " "
. += configurer.clickable_link("abilconf", attacktype, "(null)", "attacktype")
. += " damage every hit, "
. += configurer.clickable_link("abilconf", stunlocks ? "causing" : "not causing", "not causing", "stunlocks")
. += " a stunlock.<br>"
. += "<b>When going into frenzy</b>, the following text is displayed: <br>"
. += configurer.clickable_link("abilconf", frenzy_text, "(null)", "frenzy_text")
. += "<br/>"
. += configurer.sound_link("Entering frenzy", frenzy_sound, "abilconf", "frenzy_sound")
. += "<br/><b>When attacking in frenzy</b>, the following text is displayed: <br>"
. += configurer.clickable_link("abilconf", frenzy_attack, "(null)", "frenzy_attack")
. += "<br/>"
. += configurer.sound_link("Frenzy attack", frenzy_attack_sound, "abilconf", "frenzy_attack_sound")
serialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
..()
F["[path].frenzy_text"] << src.frenzy_text
F["[path].frenzy_attack"] << src.frenzy_attack
F["[path].frenzy_duration"] << src.frenzy_duration
F["[path].attack_power"] << src.attack_power
F["[path].attacktype"] << src.attacktype
F["[path].attack_cooldown"] << src.attack_cooldown
F["[path].stunlocks"] << src.stunlocks
F["[path].frenzy_sound"] << src.frenzy_sound
F["[path].frenzy_attack_sound"] << src.frenzy_attack_sound
deserialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
..()
F["[path].frenzy_text"] >> src.frenzy_text
F["[path].frenzy_attack"] >> src.frenzy_attack
F["[path].frenzy_duration"] >> src.frenzy_duration
F["[path].attack_power"] >> src.attack_power
F["[path].attacktype"] >> src.attacktype
F["[path].attack_cooldown"] >> src.attack_cooldown
F["[path].stunlocks"] >> src.stunlocks
F["[path].frenzy_sound"] >> src.frenzy_sound
F["[path].frenzy_attack_sound"] >> src.frenzy_attack_sound
clone()
var/datum/critterAbility/frenzy/A = ..()
A.frenzy_text = frenzy_text
A.frenzy_attack = frenzy_attack
A.frenzy_duration = frenzy_duration
A.attack_power = attack_power
A.attacktype = attacktype
A.attack_cooldown = attack_cooldown
A.stunlocks = stunlocks
A.frenzy_sound = frenzy_sound
A.frenzy_attack_sound = frenzy_attack_sound
return A
/datum/critterAbility/shockwave
name = "shockwave"
abstract = 0
var/datum/abilityHolder/revenant/dummyHolder
var/datum/targetable/revenantAbility/shockwave/ability
var/shockwave_text = "%src% stomps the ground!"
var/sound/shockwave_sound
New()
dummyHolder = new()
ability = new()
dummyHolder.abilities += ability
ability.holder = dummyHolder
attach()
..()
dummyHolder.owner = C
configuration(var/datum/critterCreator/configurer)
. = ..()
. += "The shockwave has a propagation ratio of "
. += configurer.clickable_link("abilconf", "[ability.propagation_percentage]%", "0%", "propagation")
. += ".<br>"
. += "The shockwave has a maximum range of "
. += configurer.clickable_link("abilconf", ability.iteration_depth, 0, "iteration")
. += ".<br>"
. += "The following text will be displayed: <br>"
. += configurer.clickable_link("abilconf", shockwave_text, "(null)", "shockwave_text")
. += "<br>"
. += configurer.sound_link("Shockwave", shockwave_sound, "abilconf", "shockwave_sound")
change_configuration(var/datum/critterCreator/configurer, var/which)
..()
switch (which)
if ("propagation")
ability.propagation_percentage = configurer.getNum("propagation percentage", ability.propagation_percentage)
if ("iteration")
ability.iteration_depth = configurer.getNum("iteration depth", ability.iteration_depth)
if ("shockwave_text")
shockwave_text = configurer.getText("shockwave text", shockwave_text)
configurer.sound_router(list("abilconf" = which), "abilconf", "shockwave_sound", src, "shockwave_sound")
use_ability()
ability.cast()
C.tokenized_message(shockwave_text)
C.play_optional_sound(shockwave_sound)
return 1
serialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
..()
F["[path].propagation"] << ability.propagation_percentage
F["[path].iteration"] << ability.iteration_depth
F["[path].shockwave_text"] << src.shockwave_text
F["[path].shockwave_sound"] << src.shockwave_sound
deserialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
..()
F["[path].propagation"] >> ability.propagation_percentage
F["[path].iteration"] >> ability.iteration_depth
F["[path].shockwave_text"] >> src.shockwave_text
F["[path].shockwave_sound"] >> src.shockwave_sound
clone()
var/datum/critterAbility/shockwave/A = ..()
A.ability.propagation_percentage = ability.propagation_percentage
A.ability.iteration_depth = ability.iteration_depth
A.shockwave_text = shockwave_text
A.shockwave_sound = shockwave_sound
return A
/datum/critterAbility/spawnCritter
name = "spawn critter"
var/obj/critter/template = null
var/stattype = null
var/spawn_text = "%src% creates a new %target%."
var/sound/spawn_sound
abstract = 0
use_ability()
var/obj/critter/D = template.clone()
C.tokenized_message(spawn_text, D)
C.play_optional_sound(spawn_sound)
D.set_loc(get_turf(C))
return 1
clone()
var/datum/critterAbility/spawnCritter/A = ..()
A.template = template.clone()
A.spawn_text = spawn_text
A.spawn_sound = spawn_sound
return A
configuration(var/datum/critterCreator/configurer)
. = ..()
. += "Spawns critter: [template ? initial(template.name) : "(null)"]."
. += configurer.clickable_link("abilconf", "Set critter from existing critter type", "", "existing")
. += "<br>"
. += configurer.clickable_link("abilconf", "Set critter from critter file", "", "file")
. += "<br>"
. += "The following text will be displayed: <br>"
. += configurer.clickable_link("abilconf", spawn_text, "(null)", "spawn_text")
. += "<br>"
. += configurer.sound_link("Spawn", spawn_sound, "abilconf", "spawn_sound")
change_configuration(var/datum/critterCreator/configurer, var/which)
..()
switch (which)
if ("existing")
var/ctype = input("Critter type", "Critter type", null) in typesof(/obj/critter) - list(/obj/critter, /obj/critter/custom)
stattype = ctype
template = new ctype
if ("file")
var/critterfile = input("Critter data", "Critter data", null) as file
var/temp = loadCustomCritterFromFile(critterfile)
if (temp)
stattype = null
template = temp
else
boutput(usr, "<span style=\"color:red\">Loading failed.</span>")
if ("spawn_text")
spawn_text = configurer.getText("spawn text", spawn_text)
configurer.sound_router(list("abilconf" = which), "abilconf", "spawn_sound", src, "spawn_sound")
serialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
..()
if (stattype)
F["[path].mode"] << 0
F["[path].stattype"] << stattype
else
F["[path].mode"] << 1
template.serialize(F, "[path].template", sandbox)
F["[path].spawn_text"] << src.spawn_text
F["[path].spawn_sound"] << src.spawn_sound
deserialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
..()
var/mode
F["[path].mode"] >> mode
if (mode)
template = new /obj/critter/custom()
template.deserialize(F, "[path].template", sandbox)
else
F["[path].stattype"] >> stattype
template = new stattype()
F["[path].spawn_text"] >> src.spawn_text
F["[path].spawn_sound"] >> src.spawn_sound
/datum/critterAbility/arcFlash
name = "lightning strike"
var/lightning_wattage = 5000
var/mobs_struck = 1
var/chains_to = 0
abstract = 0
serialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
..()
F["[path].lightning_wattage"] << lightning_wattage
F["[path].mobs_struck"] << mobs_struck
F["[path].chains_to"] << chains_to
deserialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
..()
F["[path].lightning_wattage"] >> lightning_wattage
F["[path].mobs_struck"] >> mobs_struck
F["[path].chains_to"] >> chains_to
use_ability()
if (!mobs_struck)
return 0
var/strike = mobs_struck
var/chain_depth = chains_to
var/list/previous = list()
var/list/affected = list()
for (var/mob/living/M in view(7, C))
affected += M
var/curr_W = lightning_wattage / mobs_struck
while (strike > 0 && affected.len)
strike--
var/mob/M = pick(affected)
arcFlash(C, M, curr_W)
affected -= M
previous += M
spawn(0)
while (chain_depth > 0)
sleep(2)
curr_W /= 2
chain_depth--
var/previous_copy = previous.Copy()
previous.len = 0
for (var/mob/M in previous_copy)
if (!M)
continue
affected.len = 0
for (var/mob/living/N in view(7, M))
if (!(N in previous) && M != N)
affected += N
if (affected.len)
var/mob/N = pick(affected)
arcFlash(M, N, curr_W)
previous += N
return 1
change_configuration(var/datum/critterCreator/configurer, var/which)
..()
switch(which)
if ("wattage")
lightning_wattage = configurer.getNum("wattage", lightning_wattage)
if ("mobs_struck")
mobs_struck = configurer.getNum("mobs struck", mobs_struck)
if ("chains_to")
chains_to = configurer.getNum("chaining depth", chains_to)
configuration(var/datum/critterCreator/configurer)
. = ..()
. += "<span class='attribute-name'>The</span> "
. += configurer.clickable_link("abilconf", lightning_wattage, 0, "wattage")
. += " W <span class='attribute-name'>lightning will strike randomly at </span>"
. += configurer.clickable_link("abilconf", mobs_struck, "0", "mobs_struck")
. += (mobs_struck == 1) ? " mob in view, " : " mobs in view, "
. += "<br>"
. += configurer.clickable_link("abilconf", chains_to ? "chaining on to [chains_to] more targets." : "not chaining on.", "not chaining on.", "chains_to")
clone()
var/datum/critterAbility/arcFlash/A = ..()
A.lightning_wattage = lightning_wattage
A.mobs_struck = mobs_struck
A.chains_to = chains_to
return A
/datum/critterCreator
var/obj/critter/custom/template = null
var/abilid = 0
var/static/list/presets = list("Alien" = "alien", "Cat" = "cat1", "Chicken" = "chicken", "Darkness" = "darkness", "Death" = "death", "Floating Eye" = "floateye", "Killer Tomato" = "ktomato" ,\
"Ice Spider" = "icespider", "Ice Spider Baby" = "babyicespider", "Ice Spider Queen" = "gianticespider", "Lion" = "lion",\
"Man Eater" = "maneater", "Martian" = "martian", "Martian (psychic)" = "martianP", "Martian (sapper)" = "martianSP", "Martian (soldier)" = "martianS", "Martian (warrior)" = "martianW", "Mouse" = "mouse",\
"Mutant" = "blobman", "Plasma Spore" = "spore", "Roach" = "roach", "Spider" = "spider", "Town Guard" = "townguard", "TURD" = "TURDS", \
"Weird Thing" = "ancientrobot", "Wendigo" = "wendigo",\
"Wendigo King" = "wendigoking", "Zombie" = "zombie", "Zombie (science)" = "scizombie", "Zombie (security)" = "seczombie", "cancel" = "cancel")
var/static/list/ability_cache = list()
var/static/list/death_cache = list()
var/static/list/sound_presets = list("Bang" = 'sound/effects/bang.ogg', "Beep 1" = 'sound/misc/ancientbot_beep1.ogg', "Beep 2" = 'sound/misc/ancientbot_beep2.ogg', "Beep 3" = 'sound/misc/ancientbot_beep3.ogg', "'Beware coward'" = 'sound/voice/MEbewarecoward.ogg',\
"Blob Attack (old)" = 'sound/effects/blobattack.ogg', "Blob Impact" = 'sound/effects/attackblob.ogg', "Bloody Stab 1" = 'sound/effects/bloody_stab.ogg', "Bloody Stab 2" = 'sound/effects/bloody_stabOLD.ogg',\
"Boop 1" = 'sound/machines/whistlebeep.ogg', "Boop 2" = 'sound/machines/whistlealert.ogg', "Boop 3" = 'sound/machines/twobeep.ogg', "Bubbling" = 'sound/effects/bubbles.ogg',\
"Burp 1" = 'sound/misc/burp.ogg', "Burp 2" = 'sound/misc/burp_alien.ogg', "Buzz 1" = 'sound/misc/ancientbot_buzz1.ogg', "Buzz 2" = 'sound/misc/ancientbot_buzz2.ogg', "Buzz 3" = 'sound/misc/ancientbot_grump.ogg',\
"Buzz 4" = 'sound/misc/ancientbot_grump2.ogg', "Clunk" = 'sound/effects/thunk.ogg', "Crunch 1" = 'sound/misc/loudcrunch.ogg', "Crunch 2" = 'sound/misc/loudcrunch2.ogg', \
"Crystal Break" = 'sound/effects/crystalshatter.ogg', "Crystal Impact" = 'sound/effects/crystalhit.ogg', "Crystal Step" = 'sound/misc/glass_step.ogg', \
"Ghost 1" = 'sound/effects/ghost.ogg', "Ghost 2" = 'sound/effects/ghost2.ogg', "Ghost 3" = 'sound/effects/ghostbreath.ogg', "Ghost Laugh" = 'sound/effects/ghostlaugh.ogg', "Gibbing" = 'sound/effects/gib.ogg',\
"Glitch 1" = 'sound/effects/glitchshot.ogg', "Glitch 2" = 'sound/effects/glitchy1.ogg', "Glitch 3" = 'sound/effects/glitchy2.ogg', "Glitch 4" = 'sound/effects/glitchy3.ogg', "Glitch 5" = 'sound/machines/glitch1.ogg',\
"Glitch 6" = 'sound/machines/glitch2.ogg', "Glitch 7" = 'sound/machines/glitch3.ogg', "Glitch 8" = 'sound/machines/glitch4.ogg', "Glitch 9" = 'sound/machines/glitch5.ogg', "Goose Honk" = 'sound/effects/goose.ogg',\
"Groan 1" = 'sound/voice/Zgroan1.ogg', "Groan 2" = 'sound/voice/Zgroan2.ogg', "Groan 3" = 'sound/voice/Zgroan3.ogg', "Groan 4" = 'sound/voice/Zgroan4.ogg',\
"Growl" = 'sound/voice/YetiGrowl.ogg', "Hiss" = 'sound/effects/cat_hiss.ogg', "'I hunger'" = 'sound/voice/MEhunger.ogg', "'I live'" = 'sound/voice/MEilive.ogg', "Meow" = 'sound/effects/cat.ogg', "Punch 1" = 'sound/weapons/punch1.ogg', "Punch 2" = 'sound/weapons/genhit1.ogg',\
"Punch 3" = 'sound/misc/critpunch.ogg', "Roar 1" = 'sound/misc/wendigo_roar.ogg', "Roar 2" = 'sound/misc/wendigo_scream.ogg', "Roar 3" = 'sound/voice/MEraaargh.ogg', "Roar (distant)" = 'sound/effects/mag_pandroar.ogg', "Robot gib" = 'sound/effects/robogib.ogg',\
"'Run coward'" = 'sound/voice/MEruncoward.ogg', "Shock 1" = 'sound/effects/electric_shock.ogg', "Shock 2" = 'sound/effects/elec_bzzz.ogg', "Shock 3" = 'sound/effects/elec_bigzap.ogg', "Splat" = 'sound/effects/splat.ogg', \
"Thunder" = 'sound/effects/thunder.ogg')
New()
..()
create_template()
proc/create_template()
template = new /obj/critter/custom
template.name = initial(template.name)
template.is_template = 1
proc/spawn_in()
template.clone(usr.loc)
proc/clickable(var/text, var/null_value = "(null)")
if (text != null && text != "")
if (istype(text, /datum))
if ("name" in text:vars)
return "[text:name]"
else
return "[text:type]"
else
return "[text]"
else
return "[null_value]"
proc/clickable_link(var/topic_name, var/curr_value, var/null_value = "(null)", var/topic_value = 1)
return "<a href='?src=\ref[src];[topic_name]=[topic_value]'>[clickable(curr_value, null_value)]</a>"
proc/switcher(var/value, var/current_value, var/name, var/text)
if (value == current_value)
return "<span class='active'>[text]</span>"
else
return clickable_link(name, text, null, value)
proc/attribute_clicker(var/attribute_name, var/topic_name, var/curr_value, var/pad_to = 16, var/null_value = "(null)")
var/op = "<span class='attribute-name'>[attribute_name]:"
for (var/i = length(attribute_name) + 2, i <= pad_to, i++)
op += "&nbsp;"
op += "</span>"
op += clickable_link(topic_name, curr_value, null_value)
op += "<br>"
return op
proc/getNum(var/name, var/default)
return input(usr, "New value for [name]", name, default) as num
proc/getText(var/name, var/default)
return input(usr, "New value for [name]", name, default) as text
proc/getEnum(var/name, var/default, var/list/possible)
return input(usr, "New value for [name]", name, default) in possible
proc/getTypeExclusive(var/name, var/default, var/parent_type)
return input(usr, "New value for [name]", name, default) in typesof(parent_type) - parent_type
proc/stripPath(var/typename)
var/typetext = "[typename]"
var/last = 1
var/current = findtext(typetext, "/", last)
while (current)
last = current + 1
current = findtext(typetext, "/", last)
return copytext(typetext, last)
Topic(href, href_list)
if (href_list["name"])
template.name = getText("name", template.name)
else if (href_list["desc"])
template.desc = getText("description", template.desc)
else if (href_list["health"])
template.health = getNum("health", template.health)
else if (href_list["aggressive"])
template.aggressive = !template.aggressive
else if (href_list["defensive"])
template.defensive = !template.defensive
else if (href_list["atkcarbon"])
template.atkcarbon = !template.atkcarbon
else if (href_list["atksilicon"])
template.atksilicon = !template.atksilicon
else if (href_list["mobile"])
template.mobile = !template.mobile
else if (href_list["wanderer"])
template.wanderer = !template.wanderer
else if (href_list["melee"])
template.melee = !template.melee
else if (href_list["power"])
template.attack_power = getNum("attack power", template.attack_power)
else if (href_list["atype"])
template.attack_type = getEnum("attack type", template.attack_type, list("brute", "burn", "toxin", "suffocation", "radiation"))
else if (href_list["stunp"])
template.stun_prob = getNum("stun chance", template.stun_prob)
else if (href_list["anger"])
template.anger_text = getText("charge text", template.anger_text)
else if (href_list["chase"])
template.chase_text = getText("chase text", template.chase_text)
else if (href_list["stun"])
template.stun_text = getText("stun text", template.stun_text)
else if (href_list["stunf"])
template.stun_fail_text = getText("stun fail text", template.stun_fail_text)
else if (href_list["attack"])
template.attack_text = getText("attack text", template.attack_text)
else if (href_list["corpse"])
template.gib_corpses = !template.gib_corpses
else if (href_list["death"])
template.death_text = getText("death text", template.death_text)
else if (href_list["brutevuln"])
template.brutevuln = getNum("brute damage multiplier", template.brutevuln)
else if (href_list["firevuln"])
template.firevuln = getNum("burn damage multiplier", template.firevuln)
else if (href_list["explosivevuln"])
template.explosivevuln = getNum("explosive damage multiplier", template.explosivevuln)
else if (href_list["lootamount"])
var/datum/critterLoot/L = locate(href_list["lootamount"])
if (L)
L.change_configuration(src, "amount")
else if (href_list["lootdropped"])
var/datum/critterLoot/L = locate(href_list["lootdropped"])
if (L)
L.change_configuration(src, "dropped")
else if (href_list["lootchance"])
var/datum/critterLoot/L = locate(href_list["lootchance"])
if (L)
L.change_configuration(src, "chance")
else if (href_list["lootremove"])
var/datum/critterLoot/L = locate(href_list["lootremove"])
if (L)
L.change_configuration(src, "remove")
else if (href_list["iconondeath"])
template.dead_change_icon = !template.dead_change_icon
else if (href_list["icon"])
var/I = input("Select an image file.", "Image file", null) as icon|null
if (I)
template.icon = I
template.icon_state = null
else if (href_list["icon_state"])
template.icon_state = getEnum("icon state", "", icon_states(template.icon))
else if (href_list["dead_icon"])
var/I = input("Select an image file.", "Image file", null) as icon|null
if (I)
template.dead_icon = I
template.dead_icon_state = null
else if (href_list["dead_icon_state"])
if (template.dead_icon)
template.dead_icon_state = getEnum("icon state", "", icon_states(template.dead_icon))
else
alert("Please define an icon first.")
else if (href_list["icon_preset"])
var/IS = getEnum("preset", "cancel", presets)
if (IS != "cancel")
template.icon = 'icons/misc/critter.dmi'
template.icon_state = presets[IS]
else if (href_list["abilconf"])
if (abilid)
var/datum/critterAbility/A = template.abil[abilid]
if (A)
A.change_configuration(src, href_list["abilconf"])
else if (href_list["loottable"])
template.loot_table.change_configuration(src, href_list["loottable"])
else if (href_list["color"])
template.color = input("Choose your color.", "color", template.color) as color
else if (href_list["deathconf"])
var/datum/critterDeath/A = template.on_death
if (A)
A.change_configuration(src, href_list["deathconf"])
else if (href_list["abilevent"])
if (abilid)
var/datum/critterAbility/A = template.abil[abilid]
if (A)
if (A.event)
A.event.change_configuration(src, href_list["abilevent"])
else if (href_list["ability"])
abilid = text2num(href_list["ability"])
else if (href_list["abilchange"])
if (!ability_cache.len)
build_ability_cache()
if (!abilid)
return
var/datum/critterAbility/A = template.abil[abilid]
var/datum/critterAbility/ab = input("Which ability?", "Ability", null) in ability_cache
if (A)
if (ab.type == A.type)
return
qdel(A)
A = ab.clone()
template.abil[abilid] = A
else if (href_list["ondeath"])
if (!death_cache.len)
build_death_cache()
var/datum/critterDeath/D = template.on_death
var/datum/critterDeath/ab = input("Which death event?", "On death", null) in death_cache
if (D)
if (D.type == ab.type)
return
qdel(D)
template.on_death = ab.clone()
template.on_death.C = template
else if (href_list["newabil"])
template.abil.len++
template.abil[template.abil.len] = null
else if (href_list["spawn"])
spawn_in()
else if (href_list["reset"])
template = new /obj/critter/custom
template.name = initial(template.name)
template.is_template = 1
abilid = 0
else if (href_list["filesave"])
var/fname = "adventure/CRIT_SAVE_[usr.client.ckey]_[world.time]"
if (fexists(fname))
fdel(fname)
usr.client.Export()
var/savefile/F = new /savefile(fname)
F.dir.len = 0
F.eof = -1
F << null
var/datum/sandbox/S = new()
template.serialize(F, "critter", S)
if (fexists("adventure/critter_save_[usr.client.ckey].dat"))
fdel("adventure/critter_save_[usr.client.ckey].dat")
var/target = file("adventure/critter_save_[usr.client.ckey].dat")
F.ExportText("/", target)
usr << ftp(target)
if (fexists(fname))
fdel(fname)
else if (href_list["fileload"])
var/fname = "adventure/CRIT_LOAD_[usr.client.ckey]"
if (fexists(fname))
fdel(fname)
var/target = input("Select the saved critter to load.", "Saved critter upload", null) as file
var/savefile/F = new /savefile(fname)
F.dir.len = 0
F.eof = -1
F << null
F.ImportText("/", file2text(target))
if (!F)
boutput(usr, "<span style=\"color:red\">Import failed.</span>")
else
var/datum/sandbox/S = new()
template = new()
template.deserialize(F, "critter", S)
template.is_template = 1
if (fexists(fname))
fdel(fname)
abilid = 0
else if (href_list["roundsave"])
if (!(template.name in critter_creator_controller.activeCritterTypes))
critter_creator_controller.activeCritterTypes += template.name
critter_creator_controller.activeCritterTypes[template.name] = template.clone()
boutput(usr, "<span style=\"color:blue\">Critter current state saved as [template.name]</span>")
else if (href_list["roundload"])
if (critter_creator_controller.activeCritterTypes.len)
var/cname = input("Which critter?", "Which critter?", null) in critter_creator_controller.activeCritterTypes
var/obj/critter/custom/CR = critter_creator_controller.activeCritterTypes[cname]
template = CR.clone()
boutput(usr, "<span style=\"color:blue\">Loaded [template.name].</span>")
else
boutput(usr, "<span style=\"color:red\">Nothing saved yet.</span>")
sound_router(href_list, "sounds", "anger_sound", template, "anger_sound")
sound_router(href_list, "sounds", "chase_sound", template, "chase_sound")
sound_router(href_list, "sounds", "stun_sound", template, "stun_sound")
sound_router(href_list, "sounds", "stun_fail_sound", template, "stun_fail_sound")
sound_router(href_list, "sounds", "attack_sound", template, "attack_sound")
sound_router(href_list, "sounds", "death_sound", template, "death_sound")
sound_router(href_list, "sounds", "ambient_sound", template, "ambient_sound")
show_interface(usr)
proc/build_ability_cache()
for (var/abtype in typesof(/datum/critterAbility))
var/datum/critterAbility/ab = new abtype()
if (ab.abstract)
qdel(ab)
else
ability_cache += ab
proc/build_death_cache()
for (var/abtype in typesof(/datum/critterDeath))
var/datum/critterDeath/ab = new abtype()
death_cache += ab
proc/sound_link(var/human_name, var/current, var/topic_name, var/topic_value)
return "<span class='attribute-name'>[human_name] sound: </span> [clickable_link(topic_name, current, "(null)", topic_value)] [clickable_link(topic_name, "set to null", "set to null", "[topic_value]_null")] [clickable_link(topic_name, "set to preset", "set to preset", "[topic_value]_preset")] [current != null ? clickable_link(topic_name, "test", "test", "[topic_value]_test") : null]<br>"
proc/sound_router(var/list/href_list, var/topic_name, var/topic_value, var/datum/set_on, var/varname)
if (href_list[topic_name])
if (href_list[topic_name] == topic_value)
var/inp = input("New sound.", "New sound.", null) as sound|null
if (inp)
set_on.vars[varname] = inp
else if (href_list[topic_name] == "[topic_value]_preset")
var/sound_name = input("New sound.", "New sound.", null) as null|anything in sound_presets
if (sound_name)
set_on.vars[varname] = sound_presets[sound_name]
else if (href_list[topic_name] == "[topic_value]_null")
set_on.vars[varname] = null
else if (href_list[topic_name] == "[topic_value]_test")
if (set_on.vars[varname])
boutput(usr, set_on.vars[varname])
proc/show_interface(var/mob/M)
if (!template)
create_template()
var/output = "<html><head><style>"
output += {"
body { font-family: monospace; white-space: pre-wrap; font-size: 0.5em; }
table { width: 100%; text-align: left; border: none; border-spacing: 0; border-collapse: collapse; }
tr { border:none; }
td { border:none; vertical-align: top; }
th.half, td.half { width: 50%; }
td.title { font-size: 1.4em; font-weight: bold; text-align: center; }
.subtitle { font-size: 1.2em; font-weight: bold; }
.attribute-name { font-weight: bold; }
.active { font-weight: bold; }
"}
output += "</style></head><body>"
output += "<table><tr><td colspan='2' class='title'>Critter creation kit</td></tr>"
output += "<tr><td class='half'>"
output += attribute_clicker("Name", "name", template.name)
output += attribute_clicker("Description", "desc", template.desc)
output += attribute_clicker("Health", "health", template.health)
output += "<br><span class='subtitle'>Default AI behaviour</span><br>"
output += attribute_clicker("Aggressive", "aggressive", template.aggressive ? "yes" : "no")
output += attribute_clicker("Defensive", "defensive", template.defensive ? "yes" : "no")
output += attribute_clicker("Attacks carbon", "atkcarbon", template.atkcarbon ? "yes" : "no")
output += attribute_clicker("Attacks silicon", "atksilicon", template.atksilicon ? "yes" : "no")
output += attribute_clicker("Mobile", "mobile", template.mobile ? "yes" : "no")
output += attribute_clicker("Wanderer", "wanderer", template.wanderer ? "yes" : "no")
output += "<br><span class='subtitle'>Default attack</span><br>"
output += attribute_clicker("Use melee", "melee", template.melee ? "yes" : "no")
output += attribute_clicker("Attack power", "power", template.attack_power)
output += attribute_clicker("Attack type", "atype", template.attack_type)
output += attribute_clicker("Stun chance", "stunp", "[template.stun_prob]%")
output += "<br><span class='subtitle'>Vulnerabilities</span><br>"
output += attribute_clicker("Brute", "brutevuln", "[template.brutevuln * 100]%")
output += attribute_clicker("Burn", "firevuln", "[template.firevuln * 100]%")
output += attribute_clicker("Explosive", "explosivevuln", "[template.explosivevuln * 100]%")
output += "<br><span class='subtitle'>Flavor</span><br>"
output += sound_link("Ambient", template.ambient_sound, "sounds", "ambient_sound")
output += attribute_clicker("Charge text", "anger", template.anger_text)
output += sound_link("Charge", template.anger_sound, "sounds", "anger_sound")
output += attribute_clicker("Chase text", "chase", template.chase_text)
output += sound_link("Chase", template.chase_sound, "sounds", "chase_sound")
output += attribute_clicker("Stun text", "stun", template.stun_text)
output += sound_link("Stun", template.stun_sound, "sounds", "stun_sound")
output += attribute_clicker("Stun fail text", "stunf", template.stun_fail_text)
output += sound_link("Stun fail", template.stun_fail_sound, "sounds", "stun_fail_sound")
output += attribute_clicker("Attack text", "attack", template.attack_text)
output += sound_link("Attack", template.attack_sound, "sounds", "attack_sound")
output += attribute_clicker("Death text", "death", template.death_text)
output += sound_link("Death", template.death_sound, "sounds", "death_sound")
output += "<br><span class='subtitle'>Appearance</span><br>"
output += "<span class='attribute-name'>Color (click box to change):</span><br>"
output += "<a href='?src=\ref[src];color=1' style='text-decoration:none'>"
output += "<div style='display: inline-block; width:20px; height: 20px; background-color: [template.color ? template.color : "#ffffff"]; border: 1px solid black;'>&nbsp;</div>"
output += "</a><br>"
output += "<span class='attribute-name'>Icon:</span><br/>"
var/icon/browsed_icon
browsed_icon = icon(template.icon, template.icon_state, 2)
if (template.color)
browsed_icon.Blend(template.color, ICON_MULTIPLY)
M << browse_rsc(browsed_icon, "preview_S.png")
browsed_icon = icon(template.icon, template.icon_state, 1)
if (template.color)
browsed_icon.Blend(template.color, ICON_MULTIPLY)
M << browse_rsc(browsed_icon, "preview_N.png")
browsed_icon = icon(template.icon, template.icon_state, 4)
if (template.color)
browsed_icon.Blend(template.color, ICON_MULTIPLY)
M << browse_rsc(browsed_icon, "preview_E.png")
browsed_icon = icon(template.icon, template.icon_state, 8)
if (template.color)
browsed_icon.Blend(template.color, ICON_MULTIPLY)
M << browse_rsc(browsed_icon, "preview_W.png")
output += "<img src='preview_S.png' iconstate='[template.icon_state]' icondir='SOUTH' width='32' height='32'/>"
output += "<img src='preview_N.png' iconstate='[template.icon_state]' icondir='NORTH' width='32' height='32' />"
output += "<img src='preview_E.png' iconstate='[template.icon_state]' icondir='EAST' width='32' height='32' />"
output += "<img src='preview_W.png' iconstate='[template.icon_state]' icondir='WEST' width='32' height='32' /><br>"
output += "<strong>Note: </strong> The North, East and West preview images do not show up correctly for unidirectional sprites.<br>"
output += "<a href='?src=\ref[src];icon_preset=1'>Set to preset</a><br>"
output += "<a href='?src=\ref[src];icon=1'>Set icon file</a><br>"
output += "To add directions, upload your icon in a .dmi format, with a single icon state with 4 directions.<br>"
output += attribute_clicker("Icon state", "icon_state", template.icon_state)
output += "<span class='attribute-name'>Dead icon:</span><br/>"
output += "<a href='?src=\ref[src];iconondeath=1'>[template.dead_change_icon? "Changes" : "Does not change"]</a> icon on death.<br>"
if (template.dead_change_icon)
browsed_icon = icon(template.dead_icon, template.dead_icon_state)
if (template.color)
browsed_icon.Blend(template.color, ICON_MULTIPLY)
usr << browse_rsc(browsed_icon, "preview_Death.png")
output += "<img src='preview_Death.png' iconstate='[template.dead_icon_state]' icondir='SOUTH' width='32' height='32'/><br>"
output += clickable_link("dead_icon", "Set icon file", "Set icon file")
output += "<br><span class='attribute-name'>Icon state: </span>"
output += clickable_link("dead_icon_state", template.dead_icon_state, "(null)")
output += "</td><td class='half'>"
output += "<span class='subtitle'>Abilities:</span><br>"
for (var/i = 1, i <= template.abil.len, i++)
output += switcher(i, abilid, "ability", "[i]")
output += " "
output += "<a href='?src=\ref[src];newabil=1'>Add new</a><br><br>"
if (abilid)
var/datum/critterAbility/A = template.abil[abilid]
if (A)
output += A.configuration(src)
else
output += clickable_link("abilchange", A, "Select ability type")
output += "<br><br>"
output += "<strong class='subtitle'>Other AI behaviour</strong><br>"
output += "<strong>On death, </strong>[clickable_link("ondeath", template.on_death, "do nothing")].<br>"
if (template.on_death)
output += template.on_death.configuration(src)
output += "<br/><br/><strong class='subtitle'>Loot table</strong><br>"
output += template.loot_table.configuration(src)
output += "</td></tr>"
output += "<tr><td colspan='2'>Actions: <br/>[clickable_link("spawn", "Spawn")] | [clickable_link("reset", "Reset")]<br/>"
output += "This round: [clickable_link("roundsave", "Save")] | [clickable_link("roundload", "Load")]<br/>"
output += "Into file: [clickable_link("filesave", "Save")] | [clickable_link("fileload", "Load")]</td></tr>"
output += "</table></body></html>"
M << browse(output, "window=crcreator;size=800x600")
/client/proc/critter_creator_debug()
set name = "Critter Creator (WIP)"
set category = "Debug"
set hidden = 0
var/datum/critterCreator/CR = critter_creator_controller.getCreator(src.mob)
if (CR)
CR.show_interface(src.mob)
#undef EVENT_ATTACHMENT_POINT_NONE
#undef EVENT_ATTACHMENT_POINT_MELEE
#undef EVENT_ATTACHMENT_POINT_ATTACKED