[MIRROR] Completely removes proc_holders from existence. Refactors all wizard, xeno, spider, and genetics powers to be actions. Also refactors and sorts ton of accompanying code. [MDB IGNORE] (#14666)

* Completely removes `proc_holders` from existence. Refactors all wizard, xeno, spider, and genetics powers to be actions. Also refactors and sorts ton of accompanying code.

* our changes

* yes

* 0

* Update blackmesa.dmm

Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
Co-authored-by: Gandalf <9026500+Gandalf2k15@users.noreply.github.com>
This commit is contained in:
SkyratBot
2022-07-01 21:33:30 +02:00
committed by GitHub
parent f507824ee7
commit c68fea7cba
405 changed files with 12842 additions and 11487 deletions
+84 -24
View File
@@ -85,7 +85,6 @@ GLOBAL_PROTECT(admin_verbs_admin)
/client/proc/view_opfors, //SKYRAT EDIT
/datum/admins/proc/open_borgopanel,
/datum/admins/proc/view_all_circuits,
/datum/admins/proc/view_all_sdql_spells,
/datum/admins/proc/known_alts_panel,
/datum/admins/proc/paintings_manager,
/datum/admins/proc/display_tags,
@@ -218,13 +217,13 @@ GLOBAL_PROTECT(admin_verbs_debug)
/datum/admins/proc/create_or_modify_area,
/client/proc/check_timer_sources,
/client/proc/toggle_cdn,
/client/proc/cmd_sdql_spell_menu,
/client/proc/adventure_manager,
/client/proc/load_circuit,
/client/proc/cmd_admin_toggle_fov,
/client/proc/cmd_admin_debug_traitor_objectives,
/client/proc/spawn_debug_full_crew,
/client/proc/validate_puzzgrids,
/client/proc/debug_spell_requirements,
)
GLOBAL_LIST_INIT(admin_verbs_possess, list(/proc/possess, /proc/release))
GLOBAL_PROTECT(admin_verbs_possess)
@@ -692,12 +691,29 @@ GLOBAL_PROTECT(admin_verbs_hideable)
set name = "Give Spell"
set desc = "Gives a spell to a mob."
var/which = tgui_alert(usr, "Chose by name or by type path?", "Chose option", list("Name", "Typepath"))
if(!which)
return
if(QDELETED(spell_recipient))
to_chat(usr, span_warning("The intended spell recipient no longer exists."))
return
var/list/spell_list = list()
var/type_length = length_char("/obj/effect/proc_holder/spell") + 2
for(var/spell in GLOB.spells)
spell_list[copytext_char("[spell]", type_length)] = spell
var/spell_desc = input("Choose the spell to give to that guy", "ABRAKADABRA") as null|anything in sort_list(spell_list)
if(!spell_desc)
for(var/datum/action/cooldown/spell/to_add as anything in subtypesof(/datum/action/cooldown/spell))
var/spell_name = initial(to_add.name)
if(spell_name == "Spell") // abstract or un-named spells should be skipped.
continue
if(which == "Name")
spell_list[spell_name] = to_add
else
spell_list += to_add
var/chosen_spell = tgui_input_list(usr, "Choose the spell to give to [spell_recipient]", "ABRAKADABRA", sort_list(spell_list))
if(isnull(chosen_spell))
return
var/datum/action/cooldown/spell/spell_path = which == "Typepath" ? chosen_spell : spell_list[chosen_spell]
if(!ispath(spell_path))
return
var/robeless = (tgui_alert(usr, "Would you like to force this spell to be robeless?", "Robeless Casting?", list("Force Robeless", "Use Spell Setting")) == "Force Robeless")
@@ -707,37 +723,43 @@ GLOBAL_PROTECT(admin_verbs_hideable)
return
SSblackbox.record_feedback("tally", "admin_verb", 1, "Give Spell") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
log_admin("[key_name(usr)] gave [key_name(spell_recipient)] the spell [spell_desc][robeless ? " (Forced robeless)" : ""].")
message_admins(span_adminnotice("[key_name_admin(usr)] gave [key_name_admin(spell_recipient)] the spell [spell_desc][spell_desc][robeless ? " (Forced robeless)" : ""]."))
log_admin("[key_name(usr)] gave [key_name(spell_recipient)] the spell [chosen_spell][robeless ? " (Forced robeless)" : ""].")
message_admins("[key_name_admin(usr)] gave [key_name_admin(spell_recipient)] the spell [chosen_spell][robeless ? " (Forced robeless)" : ""].")
var/spell_path = spell_list[spell_desc]
var/obj/effect/proc_holder/spell/new_spell = new spell_path()
var/datum/action/cooldown/spell/new_spell = new spell_path(spell_recipient.mind || spell_recipient)
if(robeless)
new_spell.clothes_req = FALSE
new_spell.spell_requirements &= ~SPELL_REQUIRES_WIZARD_GARB
if(spell_recipient.mind)
spell_recipient.mind.AddSpell(new_spell)
else
spell_recipient.AddSpell(new_spell)
message_admins(span_danger("Spells given to mindless mobs will not be transferred in mindswap or cloning!"))
new_spell.Grant(spell_recipient)
if(!spell_recipient.mind)
to_chat(usr, span_userdanger("Spells given to mindless mobs will belong to the mob and not their mind, \
and as such will not be transferred if their mind changes body (Such as from Mindswap)."))
/client/proc/remove_spell(mob/removal_target in GLOB.mob_list)
set category = "Admin.Fun"
set name = "Remove Spell"
set desc = "Remove a spell from the selected mob."
var/target_spell_list = length(removal_target?.mind?.spell_list) ? removal_target.mind.spell_list : removal_target.mob_spell_list
var/list/target_spell_list = list()
for(var/datum/action/cooldown/spell/spell in removal_target.actions)
target_spell_list[spell.name] = spell
if(!length(target_spell_list))
return
var/obj/effect/proc_holder/spell/removed_spell = input("Choose the spell to remove", "NO ABRAKADABRA") as null|anything in sort_list(target_spell_list)
if(removed_spell)
removal_target.mind.RemoveSpell(removed_spell)
log_admin("[key_name(usr)] removed the spell [removed_spell] from [key_name(removal_target)].")
message_admins(span_adminnotice("[key_name_admin(usr)] removed the spell [removed_spell] from [key_name_admin(removal_target)]."))
SSblackbox.record_feedback("tally", "admin_verb", 1, "Remove Spell") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
var/chosen_spell = tgui_input_list(usr, "Choose the spell to remove from [removal_target]", "ABRAKADABRA", sort_list(target_spell_list))
if(isnull(chosen_spell))
return
var/datum/action/cooldown/spell/to_remove = target_spell_list[chosen_spell]
if(!istype(to_remove))
return
qdel(to_remove)
log_admin("[key_name(usr)] removed the spell [chosen_spell] from [key_name(removal_target)].")
message_admins("[key_name_admin(usr)] removed the spell [chosen_spell] from [key_name_admin(removal_target)].")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Remove Spell") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/give_disease(mob/living/T in GLOB.mob_living_list)
set category = "Admin.Fun"
@@ -962,3 +984,41 @@ GLOBAL_PROTECT(admin_verbs_hideable)
CHECK_TICK
to_chat(admin, "[number_made] crewmembers have been created.")
/// Debug verb for seeing at a glance what all spells have as set requirements
/client/proc/debug_spell_requirements()
set name = "Show Spell Requirements"
set category = "Debug"
var/header = "<tr><th>Name</th> <th>Requirements</th>"
var/all_requirements = list()
for(var/datum/action/cooldown/spell/spell as anything in typesof(/datum/action/cooldown/spell))
if(initial(spell.name) == "Spell")
continue
var/list/real_reqs = list()
var/reqs = initial(spell.spell_requirements)
if(reqs & SPELL_CASTABLE_AS_BRAIN)
real_reqs += "Castable as brain"
if(reqs & SPELL_CASTABLE_WHILE_PHASED)
real_reqs += "Castable phased"
if(reqs & SPELL_REQUIRES_HUMAN)
real_reqs += "Must be human"
if(reqs & SPELL_REQUIRES_MIME_VOW)
real_reqs += "Must be miming"
if(reqs & SPELL_REQUIRES_MIND)
real_reqs += "Must have a mind"
if(reqs & SPELL_REQUIRES_NO_ANTIMAGIC)
real_reqs += "Must have no antimagic"
if(reqs & SPELL_REQUIRES_OFF_CENTCOM)
real_reqs += "Must be off central command z-level"
if(reqs & SPELL_REQUIRES_WIZARD_GARB)
real_reqs += "Must have wizard clothes"
all_requirements += "<tr><td>[initial(spell.name)]</td> <td>[english_list(real_reqs, "No requirements")]</td></tr>"
var/page_style = "<style>table, th, td {border: 1px solid black;border-collapse: collapse;}</style>"
var/page_contents = "[page_style]<table style=\"width:100%\">[header][jointext(all_requirements, "")]</table>"
var/datum/browser/popup = new(mob, "spellreqs", "Spell Requirements", 600, 400)
popup.set_content(page_contents)
popup.open()
@@ -1,52 +0,0 @@
/datum/component/sdql_executor
var/query = "CALL visible_message(\"<span class='warning'>The spell fizzles!</span>\") ON * IN TARGETS"
var/giver //The ckey of the user that gave this spell
var/suppress_message_admins
var/list/scratchpad = list() //Use this to store vars in between queries and casts.
var/list/saved_overrides = list()
/datum/component/sdql_executor/Initialize(giver)
src.giver = giver
//Returns the address of x without the square brackets around it.
#define RAW_ADDRESS(x) copytext("\ref[x]",2,-1)
/datum/component/sdql_executor/proc/execute(list/targets, mob/user)
if(!CONFIG_GET(flag/sdql_spells))
return
if(!length(query))
return
var/query_text = query
var/message_query = query
var/list/targets_and_user_list = list(user)
if(targets)
targets_and_user_list += targets
var/targets_and_user_string = ref_list(targets_and_user_list)
var/targets_string = ref_list(targets)
query_text = replacetextEx_char(query_text, "TARGETS_AND_USER", "[targets_and_user_string]")
message_query = replacetext_char(message_query, "TARGETS_AND_USER", (targets_and_user_list.len > 3) ? "\[<i>[targets_and_user_list.len] items</i>]" : targets_and_user_string)
query_text = replacetextEx_char(query_text, "USER", "{[RAW_ADDRESS(user)]}")
message_query = replacetextEx_char(message_query, "USER", "{[RAW_ADDRESS(user)]}")
query_text = replacetextEx_char(query_text, "TARGETS", "[targets_string]")
message_query = replacetextEx_char(message_query, "TARGETS", (targets?.len > 3) ? "\[<i>[targets.len] items</i>]" : targets_string)
query_text = replacetextEx_char(query_text, "SOURCE", "{[RAW_ADDRESS(parent)]}")
message_query = replacetextEx_char(message_query, "SOURCE", "{[RAW_ADDRESS(parent)]}")
query_text = replacetextEx_char(query_text, "SCRATCHPAD", "({[RAW_ADDRESS(src)]}.scratchpad)")
message_query = replacetextEx_char(message_query, "SCRATCHPAD", "({[RAW_ADDRESS(src)]}.scratchpad)")
if(!usr) //We need to set AdminProcCaller manually because it won't be set automatically by WrapAdminProcCall if usr is null
GLOB.AdminProcCaller = "SDQL_SPELL_OF_[user.ckey]"
GLOB.AdminProcCallCount++
world.SDQL2_query(query_text, "[key_name(user, TRUE)] (via an SDQL spell given by [giver])", "[key_name(user)] (via an SDQL spell given by [giver])", silent = suppress_message_admins)
GLOB.AdminProcCallCount--
GLOB.AdminProcCaller = null
/datum/component/sdql_executor/proc/ref_list(list/L)
if(isnull(L) || !L.len)
return "\[]"
var/ret = "\["
for(var/i in 1 to L.len-1)
ret += "{[RAW_ADDRESS(L[i])]},"
ret += "{[RAW_ADDRESS(L[L.len])]}]"
return ret
#undef RAW_ADDRESS
@@ -1,77 +0,0 @@
/// An admin verb to view all sdql spells, plus useful information
/datum/admins/proc/view_all_sdql_spells()
set category = "Admin.Game"
set name = "View All SDQL Spells"
if(CONFIG_GET(flag/sdql_spells) || tgui_alert(usr, "SDQL spells are disabled. Open the admin panel anyways?", "SDQL Admin Panel", list("Yes", "No")) == "Yes")
var/static/datum/SDQL_spell_panel/SDQL_spell_panel = new
SDQL_spell_panel.ui_interact(usr)
/datum/SDQL_spell_panel
/datum/SDQL_spell_panel/ui_static_data(mob/user)
var/list/data = list()
data["spells"] = list()
for (var/obj/effect/proc_holder/spell/spell as anything in GLOB.sdql_spells)
var/mob/living/owner = spell.owner.resolve()
var/datum/component/sdql_executor/executor = spell.GetComponent(/datum/component/sdql_executor)
if(!executor)
continue
data["spells"] += list(list(
"ref" = REF(spell),
"name" = "[spell]",
"owner" = owner,
"ownerRef" = REF(owner),
"creator" = executor.giver
))
return data
/datum/SDQL_spell_panel/ui_act(action, list/params)
. = ..()
if (.)
return .
switch(action)
if("edit_spell")
var/obj/effect/proc_holder/spell/spell = locate(params["spell"])
if(!spell)
to_chat(usr, span_warning("That spell no longer exists!"))
return
var/datum/component/sdql_executor/executor = spell.GetComponent(/datum/component/sdql_executor)
if(!executor)
to_chat(usr, span_warning("[spell][spell.p_s()] SDQL executor component is gone!"))
return
if(usr.ckey == executor.giver || tgui_alert(usr, "You didn't create this SDQL spell. Edit it anyways?", "SDQL Admin Panel", list("Yes", "No")) == "Yes")
usr.client?.cmd_sdql_spell_menu(spell)
if("follow_owner")
var/mob/living/owner = locate(params["owner"])
if(!owner)
to_chat(usr, span_warning("That mob no longer exists!"))
return
usr.client?.admin_follow(owner)
if("vv_spell")
var/obj/effect/proc_holder/spell/spell = locate(params["spell"])
if(!spell)
to_chat(usr, span_warning("That spell no longer exists!"))
return
usr.client?.debug_variables(spell)
if("open_player_panel")
var/mob/living/owner = locate(params["owner"])
if(!owner)
to_chat(usr, span_warning("That mob no longer exists!"))
return
usr.client?.holder?.show_player_panel(owner)
return TRUE
/datum/SDQL_spell_panel/ui_state(mob/user)
return GLOB.admin_state
/datum/SDQL_spell_panel/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "SDQLSpellAdminPanel")
ui.open()
@@ -1,932 +0,0 @@
GLOBAL_LIST_INIT_TYPED(sdql_spells, /obj/effect/proc_holder/spell, list())
/client/proc/cmd_sdql_spell_menu(target in GLOB.mob_list)
set name = "Give/Edit SDQL spell"
set hidden = TRUE
if(CONFIG_GET(flag/sdql_spells))
var/datum/give_sdql_spell/ui = new(usr, target)
ui.ui_interact(usr)
else
to_chat(usr, span_warning("SDQL spells are disabled."))
/datum/give_sdql_spell
var/client/user
var/mob/living/target_mob
var/obj/effect/proc_holder/spell/target_spell
var/spell_type
var/list/saved_vars = list("query" = "", "suppress_message_admins" = FALSE)
var/list/list_vars = list()
var/list/parse_result = null
var/alert
//This list contains all the vars that it should be okay to edit from the menu
var/static/list/editable_spell_vars = list(
"action_background_icon_state",
"action_icon_state",
"action_icon",
"active_msg",
"aim_assist",
"antimagic_allowed",
"base_icon_state",
"centcom_cancast",
"charge_max",
"charge_type",
"clothes_req",
"cone_level",
"deactive_msg",
"desc",
"drawmessage",
"dropmessage",
"hand_path",
"hand_var_overrides",
"holder_var_amount",
"holder_var_type",
"human_req",
"include_user",
"inner_radius",
"invocation_emote_self",
"invocation_type",
"invocation",
"max_targets",
"message",
"name",
"nonabstract_req",
"overlay_icon_state",
"overlay_icon",
"overlay_lifespan",
"overlay",
"phase_allowed",
"player_lock",
"projectile_type",
"projectile_amount",
"projectile_var_overrides",
"projectiles_per_fire",
"random_target_priority",
"random_target",
"range",
"ranged_mousepointer",
"respect_density",
"selection_type",
"self_castable",
"smoke_amt",
"smoke_spread",
"sound",
"sparks_amt",
"sparks_spread",
"stat_allowed",
"still_recharging_msg",
"target_ignore_prev",
)
//If a spell creates a datum with vars it overrides, this list should contain an association with the variable containing the path of the created datum.
var/static/list/special_list_vars = list(
"projectile_var_overrides" = "projectile_type",
"hand_var_overrides" = "hand_path",
)
var/static/list/special_var_lists = list(
"projectile_type" = "projectile_var_overrides",
"hand_path" = "hand_var_overrides",
)
var/static/list/enum_vars = list(
"invocation_type" = list(INVOCATION_NONE, INVOCATION_WHISPER, INVOCATION_SHOUT, INVOCATION_EMOTE),
"selection_type" = list("view", "range"),
"smoke_spread" = list(0, 1, 2, 3),
"random_target_priority" = list(0, 1),
)
//base64 representations of any icons that may need to be displayed
var/action_icon_base64
var/projectile_icon_base64
var/hand_icon_base64
var/overlay_icon_base64
var/mouse_icon_base64
/datum/give_sdql_spell/New(user, target)
if(!CONFIG_GET(flag/sdql_spells))
to_chat(user, span_warning("SDQL spells are disabled."))
qdel(src)
return
src.user = CLIENT_FROM_VAR(user)
if(istype(target, /obj/effect/proc_holder/spell))
target_spell = target
var/mob/living/spell_owner = target_spell.owner.resolve()
if(spell_owner)
target_mob = spell_owner
else
to_chat(user, span_warning("[target_spell] does not have an owner, or its owner was qdelled. This REALLY shouldn't happen."))
qdel(src)
return
else if(isliving(target))
target_mob = target
else
to_chat(user, span_warning("Invalid target."))
qdel(src)
return
if(target_spell)
load_vars_from(target_spell)
/datum/give_sdql_spell/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "SDQLSpellMenu", "Give SDQL Spell")
ui.open()
ui.set_autoupdate(FALSE)
/datum/give_sdql_spell/ui_state(mob/user)
return GLOB.admin_state
/datum/give_sdql_spell/ui_status(mob/user, datum/ui_state/state)
if(QDELETED(target_mob))
return UI_CLOSE
return ..()
/datum/give_sdql_spell/ui_close(mob/user)
qdel(src)
#define SANITIZE_NULLIFY 0
#define SANITIZE_STRINGIFY 1
/datum/give_sdql_spell/ui_data(mob/user, params)
var/list/data = list()
if(target_spell)
data["type"] = copytext("[target_spell.type]", 31, -5)
data["fixed_type"] = TRUE
else
data["type"] = spell_type
data["fixed_type"] = FALSE
data["saved_vars"] = saved_vars
data["list_vars"] = json_sanitize_list_vars(list_vars, SANITIZE_STRINGIFY)
if(parse_result)
data["parse_errors"] = parse_result["parse_errors"]
data["parsed_type"] = parse_result["type"]
data["parsed_vars"] = parse_result["vars"]
data["parsed_list_vars"] = json_sanitize_list_vars(parse_result["list_vars"], SANITIZE_STRINGIFY)
else
data["parse_errors"] = null
data["parsed_type"] = null
data["parsed_vars"] = null
data["parsed_list_vars"] = null
data["action_icon"] = action_icon_base64
data["projectile_icon"] = projectile_icon_base64
data["hand_icon"] = hand_icon_base64
data["overlay_icon"] = overlay_icon_base64
data["mouse_icon"] = mouse_icon_base64
data["alert"] = alert
alert = ""
return data
/datum/give_sdql_spell/ui_static_data(mob/user)
return list(
"types" = list("aimed", "aoe_turf", "cone", "cone/staggered", "pointed", "self", "targeted", "targeted/touch"),
"tooltips" = list(
"query" = "The SDQL query that is executed. Certain keywords are specific to SDQL spell queries.\n\
$type\n\
USER is replaced with a reference to the user of the spell.\n\
TARGETS_AND_USER is replaced with the combined references from TARGETS and USER.\n\
SOURCE is replaced with a reference to this spell, allowing you to refer to and edit variables within it.\n\
SCRATCHPAD is a list used to store variables between individual queries within the same cast or between multiple casts.\n\
NOTE: The SDQL keywords usr and marked do not work.",
"query_aimed" = "TARGETS is replaced with a list containing a reference to the atom hit by the fired projectile.",
"query_aoe_turf" = "TARGETS is replaced with a list containing references to every atom in the spell's area of effect.",
"query_cone" = "TARGETS is replaced with a list containing references to every atom in the cone produced by the spell.",
"query_cone/staggered" = "The query will be executed once for every level of the cone produced by the spell.\n\
TARGETS is replaced with a list containing references to every atom in the given level of the cone.",
"query_pointed" = "TARGETS is replaced with a list containing a reference to the targeted atom.",
"query_self" = "TARGETS is null.",
"query_targeted" = "TARGETS is replaced with a list containing a reference(s) to the targeted mob(s).",
"query_targeted_touch" = "TARGETS is replaced with a list containing a reference to the atom hit with the touch attack.",
"suppress_message_admins" = "If this is true, the spell will not print out its query to admins' chat panels.\n\
The query will still be output to the game log.",
"charge_type" = "How the spell's charge works. This affects how charge_max is used.\n\
When set to \"recharge\", charge_max is the time in deciseconds between casts of the spell.\n\
When set to \"charges\", the user can only use the spell a number of times equal to charge_max.\n\
When set to \"holder_var\", charge_max is not used. holder_var_type and holder_var_amount are used instead.\n",
"holder_var_type" = "When charge_type is set to \"holder_var\", this is the name of the var that is modified each time the spell is cast.\n\
If this is set to \"bruteloss\", \"fireloss\", \"toxloss\", or \"oxyloss\", the user will take the corresponding damage.\n\
If this is set to \"stun\", \"knockdown\", \"paralyze\", \"immobilize\", or \"unconscious\", the user will suffer the corresponding status effect.\n\
If this is set to anything else, the variable with the appropriate name will be modified.",
"holder_var_amount" = "The amount of damage taken, the duration of status effect inflicted, or the change made to any other variable.",
"clothes_req" = "Whether the user has to be wearing wizard robes to cast the spell.",
"human_req" = "Whether the user has to be a human to cast the spell. Redundant when clothes_req is true.",
"nonabstract_req" = "If this is true, the spell cannot be cast by brains and pAIs.",
"stat_allowed" = "Whether the spell can be cast if the user is unconscious or dead.",
"phase_allowed" = "Whether the spell can be cast while the user is jaunting or bloodcrawling.",
"antimagic_allowed" = "Whether the spell can be cast while the user is affected by anti-magic effects.",
"invocation_type" = "How the spell is invoked.\n\
When set to \"none\", the user will not state anything when invocating.\n\
When set to \"whisper\", the user whispers the invocation, as if with the whisper verb.\n\
When set to \"shout\", the user says the invocation, as if with the say verb.\n\
When set to \"emote\", a visible message is produced.",
"invocation" = "What the user says, whispers, or emotes when using the spell.",
"invocation_emote_self" = "What the user sees in their own chat when they use the spell.",
"selection_type" = "Whether the spell can target any mob in range, or only visible mobs in range.",
"range" = "The spell's range, in tiles.",
"message" = "What mobs affected by the spell see in their chat.\n\
Keep in mind, just because a mob is affected by the spell doesn't mean the query will have any effect on them.",
"player_lock" = "If false, simple mobs can use the spell.",
"overlay" = "Whether an overlay is drawn atop atoms affectecd by the spell.\n\
Keep in mind, just because an atom is affected by the spell doesn't mean the query will have any effect on it.",
"overlay_lifetime" = "The amount of time in deciseconds the overlay will persist.",
"sparks_spread" = "Whether the spell produces sparks when cast.",
"smoke_spread" = "The kind of smoke, if any, the spell produces when cast.",
"centcom_cancast" = "If true, the spell can be cast on the centcom Z-level.",
"max_targets" = "The maximum number of mobs the spell can target.",
"target_ignore_prev" = "If false, the same mob can be targeted multiple times.",
"include_user" = "If true, the user can target themselves with the spell.",
"random_target" = "If true, the spell will target a random mob(s) in range.",
"random_target_priority" = "Whether the spell will target random mobs in range or the closest mobs in range.",
"inner_radius" = "If this is a non-negative number, the spell will not affect atoms within that many tiles of the user.",
"ranged_mousepointer" = "The icon used for the mouse when aiming the spell.",
"deactive_mesg" = "The message the user sees when canceling the spell.",
"active_msg" = "The message the user sees when activating the spell.",
"projectile_amount" = "The maximum number of projectiles the user can fire with each cast of the spell.",
"projectiles_per_fire" = "The amount of projectiles fired with each click of the mouse.",
"projectile_var_overrides" = "The fired projectiles will have the appropriate variables overridden by the corresponding values in this associative list.\n\
You should probably set \"name\", \"icon\", and \"icon_state\".\n\
Refer to code/modules/projectiles/projectile.dm to see what other vars you can override.",
"cone_level" = "How many tiles out the cone will extend.",
"respect_density" = "If true, the cone produced by the spell is blocked by walls.",
"self_castable" = "If true, the user can cast the spell on themselves.",
"aim_assist" = "If true, the spell has turf-based aim assist.",
"drawmessage" = "The message the user sees when activating the spell.",
"dropmessage" = "The message the user sees when canceling the spell.",
"hand_var_overrides" = "The touch attack will have the appropriate variables overridden by the corresponding values in this associative list.\n\
You should probably set \"name\", \"desc\", \"catchphrase\", \"on_use_sound\" \"icon\", \"icon_state\", and \"inhand_icon_state\".\n\
Refer to code/modules/spells/spell_types/godhand.dm to see what other vars you can override.",
"scratchpad" = "This list can be used to store variables between individual queries within the same cast or between casts.\n\
You can declare variables from this menu for convenience. To access this list in a query, use the identifier \"SOURCE.scratchpad\".\n\
Refer to the _list procs defined in code/modules/admin/verbs/SDQL2/SDQL_2_wrappers.dm for information on how to modify and edit list vars from within a query.",
),
)
#define LIST_VAR_FLAGS_TYPED 1
#define LIST_VAR_FLAGS_NAMED 2
/datum/give_sdql_spell/proc/load_vars_from(obj/effect/proc_holder/spell/sample)
var/datum/component/sdql_executor/executor = sample.GetComponent(/datum/component/sdql_executor)
if(!executor)
CRASH("[sample]'s SDQL executor component went missing!")
saved_vars["query"] = executor.query
saved_vars["suppress_message_admins"] = executor.suppress_message_admins
load_list_var(executor.scratchpad, "scratchpad")
for(var/V in sample.vars&editable_spell_vars)
if(islist(sample.vars[V]))
if(special_list_vars[V])
var/list/saved_overrides = executor.saved_overrides[V]
if(saved_overrides)
list_vars[V] = saved_overrides.Copy()
icon_needs_updating("[V]/icon")
else
saved_vars[V] = sample.vars[V]
icon_needs_updating(V)
/datum/give_sdql_spell/proc/load_list_var(list/L, list_name)
list_vars[list_name] = list()
for(var/V in L)
if(islist(L[V]))
list_vars[list_name][V] = list("type" = "list", "value" = null)
list_vars |= load_list_var(L[V], "[list_name]/[V]")
else if(isnum(L[V]))
list_vars[list_name][V] = list("type" = "num", "value" = L[V])
else if(ispath(L[V]))
list_vars[list_name][V] = list("type" = "path", "value" = L[V])
else if(isicon(L[V]))
list_vars[list_name][V] = list("type" = "icon", "value" = L[V])
else if(istext(L[V]) || isfile(L[V]))
list_vars[list_name][V] = list("type" = "string", "value" = L[V])
else if(istype(L[V], /datum))
list_vars[list_name][V] = list("type" = "ref", "value" = L[V])
else if(isnull(L[V]))
list_vars[list_name][V] = list("type" = "num", "value" = 0)
alert = "Could not determine the type for [list_name]/[V]! Be sure to set it correctly, or you may cause unnecessary runtimes!"
/datum/give_sdql_spell/ui_act(action, params, datum/tgui/ui)
if(..())
return
. = TRUE
switch(action)
if("type")
if(!target_spell)
spell_type = params["path"]
load_sample()
if("variable")
var/V = params["name"]
if(V == "holder_var_type")
if(!holder_var_validate(params["value"]))
return
saved_vars[V] = params["value"]
icon_needs_updating(V)
if("bool_variable")
saved_vars[params["name"]] = !saved_vars[params["name"]]
if("path_variable")
var/new_path = tgui_input_list(user, "Select type.", "Add SDQL Spell", typesof(text2path(params["root_path"])))
if(isnull(new_path))
return
saved_vars[params["name"]] = new_path
var/datum/sample = new new_path
var/list/overrides = list_vars[special_var_lists[params["name"]]]
overrides = overrides&sample.vars
qdel(sample)
icon_needs_updating(params["name"])
if("list_variable_add")
if(!list_vars[params["list"]])
list_vars[params["list"]] = list()
if(special_list_vars[params["list"]])
var/path = saved_vars[special_list_vars[params["list"]]]
var/datum/sample = new path
var/list/choosable_vars = map_var_list(sample.vars-list_vars[params["list"]], sample)
var/chosen_var = tgui_input_list(user, "Select variable to add.", "Add SDQL Spell", sort_list(choosable_vars))
if(chosen_var)
if(islist(sample.vars[choosable_vars[chosen_var]]))
list_vars[params["list"]][choosable_vars[chosen_var]] = list("type" = "list", "value" = null, "flags" = LIST_VAR_FLAGS_TYPED|LIST_VAR_FLAGS_NAMED)
list_vars["[params["list"]]/[choosable_vars[chosen_var]]"] = list()
else if(isnum(sample.vars[choosable_vars[chosen_var]]))
list_vars[params["list"]][choosable_vars[chosen_var]] = list("type" = "num", "value" = sample.vars[choosable_vars[chosen_var]], "flags" = LIST_VAR_FLAGS_TYPED|LIST_VAR_FLAGS_NAMED)
else if(ispath(sample.vars[choosable_vars[chosen_var]]))
list_vars[params["list"]][choosable_vars[chosen_var]] = list("type" = "path", "value" = sample.vars[choosable_vars[chosen_var]], "flags" = LIST_VAR_FLAGS_TYPED|LIST_VAR_FLAGS_NAMED)
else if(isicon(sample.vars[choosable_vars[chosen_var]]))
list_vars[params["list"]][choosable_vars[chosen_var]] = list("type" = "icon", "value" = sample.vars[choosable_vars[chosen_var]], "flags" = LIST_VAR_FLAGS_TYPED|LIST_VAR_FLAGS_NAMED)
else if(istext(sample.vars[choosable_vars[chosen_var]]) || isfile(sample.vars[choosable_vars[chosen_var]]))
list_vars[params["list"]][choosable_vars[chosen_var]] = list("type" = "string", "value" = sample.vars[choosable_vars[chosen_var]], "flags" = LIST_VAR_FLAGS_TYPED|LIST_VAR_FLAGS_NAMED)
else if(istype(sample.vars[choosable_vars[chosen_var]], /datum))
list_vars[params["list"]][choosable_vars[chosen_var]] = list("type" = "ref", "value" = null, "flags" = LIST_VAR_FLAGS_TYPED|LIST_VAR_FLAGS_NAMED)
alert = "[params["list"]]/[choosable_vars[chosen_var]] is a reference! Be sure to set it correctly, or you may cause unnecessary runtimes!"
else if(isnull(sample.vars[choosable_vars[chosen_var]]))
list_vars[params["list"]][choosable_vars[chosen_var]] = list("type" = "num", "value" = 0, "flags" = LIST_VAR_FLAGS_NAMED)
alert = "Could not determine the type for [params["list"]]/[choosable_vars[chosen_var]]! Be sure to set it correctly, or you may cause unnecessary runtimes!"
else
alert = "[params["list"]]/[choosable_vars[chosen_var]] is not of a supported type!"
icon_needs_updating("[params["list"]]/[choosable_vars[chosen_var]]")
qdel(sample)
else
if(!list_vars[params["list"]]["new_var"])
list_vars[params["list"]] += list("new_var" = list("type" = "num", "value" = 0, "flags" = 0))
else
alert = "Rename or remove [params["list"]]/new_var before attempting to add another variable to this list!"
if("list_variable_remove")
remove_list_var(params["list"], params["name"])
if("list_variable_rename")
rename_list_var(params["list"], params["name"], params["new_name"])
if("list_variable_change_type")
change_list_var_type(params["list"], params["name"], params["value"])
if("list_variable_change_value")
set_list_var(params["list"], params["name"], params["value"])
icon_needs_updating("[params["list"]]/[params["name"]]")
if("list_variable_change_bool")
toggle_list_var(params["list"], params["name"])
if("list_variable_set_ref")
set_list_ref_var(params["list"], params["name"])
if("save")
var/f = file("data/TempSpellUpload")
fdel(f)
WRITE_FILE(f, json_encode(list("type" = spell_type, "vars" = saved_vars, "list_vars" = json_sanitize_list_vars(list_vars))))
user << ftp(f,"[replacetext_char(saved_vars["name"], " ", "_")].json")
if("load")
var/spell_file = input("Pick spell json file:", "File") as null|file
if(!spell_file)
return
var/filedata = file2text(spell_file)
var/json = json_decode(filedata)
if(!json)
alert = "JSON decode error!"
return
parse_result = load_from_json(json)
var/list/parse_errors = parse_result["parse_errors"]
if(!parse_errors.len)
finalize_load()
if("close_error")
parse_result = null
if("load_despite_error")
finalize_load()
if("confirm")
if(target_spell)
reassign_vars(target_spell)
target_spell.action.UpdateButtons()
log_admin("[key_name(user)] edited the SDQL spell \"[target_spell]\" owned by [key_name(target_mob)].")
else
var/new_spell = give_spell()
log_admin("[key_name(user)] gave the SDQL spell \"[new_spell]\" to [key_name(target_mob)].")
ui.close()
/datum/give_sdql_spell/proc/load_sample()
var/path = text2path("/obj/effect/proc_holder/spell/[spell_type]/sdql")
var/datum/sample = new path
if(spell_type)
load_vars_from(sample)
qdel(sample)
/datum/give_sdql_spell/proc/finalize_load()
spell_type = parse_result["type"]
load_sample()
saved_vars = parse_result["vars"] | saved_vars
list_vars = parse_result["list_vars"] | list_vars
parse_result = null
icon_needs_updating("everything")
//Change all references in the list vars, either to null (for saving) or to their string representation (for display)
/datum/give_sdql_spell/proc/json_sanitize_list_vars(list/list_vars, mode = SANITIZE_NULLIFY)
var/list/temp_list_vars = deep_copy_list(list_vars)
for(var/V in temp_list_vars)
var/list/L = temp_list_vars[V]
for(var/W in L)
if(temp_list_vars[V][W]["type"] == "ref")
switch(mode)
if(SANITIZE_NULLIFY)
temp_list_vars[V][W]["value"] = null
if(SANITIZE_STRINGIFY)
if(temp_list_vars[V][W]["value"])
temp_list_vars[V][W]["value"] = "[temp_list_vars[V][W]["value"]]"
else
temp_list_vars[V][W]["value"] = "null"
return temp_list_vars
#undef SANITIZE_NULLIFY
#undef SANITIZE_STRINGIFY
/datum/give_sdql_spell/proc/load_from_json(json)
var/list/result_vars = list()
var/list/result_list_vars = list()
var/list/parse_errors = list()
. = list("type" = "",
"vars" = result_vars,
"list_vars" = result_list_vars,
"parse_errors" = parse_errors)
if(!json["type"])
parse_errors += "The \"type\" property is missing from the json file"
return
var/temp_type = json["type"]
var/datum/D = text2path("/obj/effect/proc_holder/spell/[temp_type]/sdql")
if(!ispath(D))
parse_errors += "[temp_type] is not a valid SDQL spell type"
return
if(target_spell)
if(!istype(target_spell, D))
parse_errors += "You cannot change the type of an existing spell"
if(!json["vars"])
parse_errors += "The \"vars\" property is missing from the json file"
if(!islist(json["vars"]))
parse_errors += "The \"vars\" property must be a json object"
if(!json["list_vars"])
parse_errors += "The \"list_vars\" property is missing from the json file"
if(!islist(json["list_vars"]))
parse_errors += "The \"list_vars\" property must be a json object"
if(parse_errors.len)
return
.["type"] = temp_type
var/list/temp_vars = json["vars"]
var/list/temp_list_vars = json["list_vars"]
D = new D
for(var/V in temp_vars)
if(!istext(V))
parse_errors += "JSON property names must be text ([V] is not text)"
continue
if(V == "query")
if(!istext(temp_vars[V]))
parse_errors += "The value of \"query\" must be text"
continue
result_vars[V] = temp_vars[V]
continue
if(V == "suppress_message_admins")
if(!isnum(temp_vars[V]))
parse_errors += "The value of \"suppress_message_admins\" must be a number"
continue
result_vars[V] = !!temp_vars[V]
continue
if(!(V in editable_spell_vars))
parse_errors += "\"[V]\" is not an editable variable"
continue
if(!(V in D.vars)) //D.vars[V] can runtime unlike V in D.vars
parse_errors += "Spells of type \"[temp_type]\" have no such var [V]"
continue
if(islist(D.vars[V]))
parse_errors += "[D.type]/[V] is a list; vars.[V] should be in the \"list_vars\" property"
continue
if(istext(D.vars[V]))
if(!istext(temp_vars[V]))
parse_errors += "[D.type]/[V] is text; vars.[V] has been converted to text"
temp_vars[V] = "[temp_vars[V]]"
continue
if(V=="holder_var_type")
var/potential_alert = holder_var_validate(temp_vars[V], TRUE)
if(potential_alert)
parse_errors += potential_alert
continue
if(isicon(D.vars[V]))
if(!istext(temp_vars[V]))
parse_errors += "[D.type]/[V] is an icon; vars.[V] has been converted to text"
temp_vars[V] = "[temp_vars[V]]"
if(!fexists(temp_vars[V]))
parse_errors += "[D.type]/[V] is an icon; no such file [temp_vars[V]] exists on the server"
continue
if(ispath(D.vars[V]))
if(!istext(temp_vars[V]))
parse_errors += "[D.type]/[V] is a path; vars.[V] has been converted to text"
temp_vars[V] = "[temp_vars[V]]"
var/path = text2path(temp_vars[V])
if(!path)
parse_errors += "[D.type]/[V] is a path; vars.[V] ([temp_vars[V]]) does not correspond to an existing type"
continue
if(!ispath(path, D.vars[V]))
parse_errors += "[D.type]/[V] is a path; vars.[V] ([path]) is not derived from [D.vars[V]]"
continue
if(isnum(D.vars[V]))
if(!isnum(temp_vars[V]))
parse_errors += "[D.type]/[V] is a number; vars.[V] should be a number"
continue
if(enum_vars[V])
var/list/enum = enum_vars[V]
if(!enum.Find(temp_vars[V]))
parse_errors += "[D.type]/[V] is an enumeration; vars.[V] should be one of: [english_list(enum, and_text = " or ")]"
continue
result_vars[V] = temp_vars[V]
for(var/V in temp_list_vars)
if(!istext(V))
parse_errors += "JSON property names must be text ([V] is not text)"
continue
if(!islist(temp_list_vars[V]))
parse_errors += "list_vars.[V] should be a json object"
continue
if(special_list_vars[V] && (V in D.vars))
var/sample_path = D.vars[special_list_vars[V]]
var/temp_path
if(temp_vars[special_list_vars[V]])
temp_path = temp_vars[special_list_vars[V]]
temp_path = text2path(temp_path)
if(!temp_path)
parse_errors += "[D.type]/[special_list_vars[V]] is a path; vars.[temp_vars[special_list_vars[V]]] (temp_vars[special_list_vars[V]]) does not correspond to an existing type"
else if(!ispath(temp_path, D.vars[special_list_vars[V]]))
parse_errors += "[D.type]/[special_list_vars[V]] is a path; vars.[special_list_vars[V]] ([temp_path]) is not derived from [D.vars[special_list_vars[V]]]"
else
sample_path = temp_path
result_list_vars[V] = list()
var/datum/sample = new sample_path
for(var/W in temp_list_vars[V])
if(!istext(W))
parse_errors += "JSON property names must be text ([W] in list_vars.[V] is not text)"
continue
if(!(W in sample.vars))
parse_errors += "[sample.type] has no such var \"[W]\""
continue
if(!(islist(temp_list_vars[V][W]) && istext(temp_list_vars[V][W]["type"]) && (istext(temp_list_vars[V][W]["value"]) || isnum(temp_list_vars[V][W]["value"]) || isnull(temp_list_vars[V][W]["value"])) && isnum(temp_list_vars[V][W]["flags"])))
parse_errors += "[V]/[W] is not of the form {type: string, value: num|string|null, flags: number}"
continue
if(!(temp_list_vars[V][W]["flags"] & LIST_VAR_FLAGS_NAMED))
parse_errors += "[V]/[W] did not have the LIST_VAR_FLAGS_NAMED flag set; it has been set"
temp_list_vars[V][W]["flags"] |= LIST_VAR_FLAGS_NAMED
if(temp_list_vars[V][W]["flags"] & ~(LIST_VAR_FLAGS_NAMED | LIST_VAR_FLAGS_TYPED))
parse_errors += "[V]/[W] has unused bit flags set; they have been unset"
temp_list_vars[V][W]["flags"] &= LIST_VAR_FLAGS_NAMED | LIST_VAR_FLAGS_TYPED
if(!(temp_list_vars[V][W]["flags"] & LIST_VAR_FLAGS_TYPED))
if(isnull(sample.vars[W]))
continue
parse_errors += "[sample.type]/[W] is not null; it has had the LIST_VAR_FLAGS_TYPED flag set"
temp_list_vars[V][W]["flags"] |= LIST_VAR_FLAGS_TYPED
if(islist(sample.vars[W]))
temp_list_vars[V][W]["type"] = "list"
else if(isnum(sample.vars[W]))
temp_list_vars[V][W]["type"] = "num"
else if(istext(sample.vars[W]))
temp_list_vars[V][W]["type"] = "string"
else if(ispath(sample.vars[W]))
temp_list_vars[V][W]["type"] = "path"
else if(isicon(sample.vars[W]))
temp_list_vars[V][W]["type"] = "icon"
else if(istype(sample.vars[W], /datum))
temp_list_vars[V][W]["type"] = "ref"
temp_list_vars[V][W]["value"] = null
else
parse_errors += "[sample.type]/[W] is not of a supported type"
continue
if(islist(sample.vars[W]))
if(temp_list_vars[V][W]["type"] != "list")
parse_errors += "[sample.type]/[W] is a list; list_vars.[V].[W].type has been converted to \"list\""
temp_list_vars[V][W]["type"] = "list"
if(!istext(temp_list_vars[V][W]["value"]))
parse_errors += "list_vars.[V].[W].type is \"list\"; list_vars.[V].[W].value ([temp_list_vars[V][W]["value"]]) has been converted to text"
temp_list_vars[V][W]["value"] = "[temp_list_vars[V][W]["value"]]"
if(!temp_list_vars[temp_list_vars[V][W]["value"]])
parse_errors += "list_vars.[V].[W].type is \"list\"; there is no property of list_vars whose name is list_vars.[V].[W].value ([temp_list_vars[V][W]["value"]])"
continue
else if(isnum(sample.vars[W]))
if(temp_list_vars[V][W]["type"] != "num")
parse_errors += "[sample.type]/[W] is a number; list_vars.[V].[W].type has been converted to \"num\""
temp_list_vars[V][W]["type"] = "num"
if(!isnum(temp_list_vars[V][W]["value"]))
parse_errors += "list_vars.[V].[W].type is \"num\"; list_vars.[V].[W].value ([temp_list_vars[V][W]["value"]]) should be a number"
continue
else if(istext(sample.vars[W]))
if(temp_list_vars[V][W]["type"] != "string")
parse_errors += "[sample.type]/[W] is text; list_vars.[V].[W].type has been converted to \"string\""
temp_list_vars[V][W]["type"] = "string"
if(!istext(temp_list_vars[V][W]["value"]))
parse_errors += "list_vars.[V].[W].type is \"list\"; list_vars.[V].[W].value ([temp_list_vars[V][W]["value"]]) has been converted to text"
temp_list_vars[V][W]["value"] = "[temp_list_vars[V][W]["value"]]"
else if(ispath(sample.vars[W]))
if(temp_list_vars[V][W]["type"] != "path")
parse_errors += "[sample.type]/[W] is a path; list_vars.[V].[W].type has been converted to \"path\""
temp_list_vars[V][W]["type"] = "path"
if(!istext(temp_list_vars[V][W]["value"]))
parse_errors += "list_vars.[V].[W].type is \"path\"; list_vars.[V].[W].value ([temp_list_vars[V][W]["value"]]) has been converted to text"
temp_list_vars[V][W]["value"] = "[temp_list_vars[V][W]["value"]]"
temp_path = text2path(temp_list_vars[V][W]["value"])
if(!ispath(temp_path))
parse_errors += "list_vars.[W].[W].type is \"path\"; list_vars.[V].[W].value ([temp_list_vars[V][W]["value"]]) does not correspond to an existing type"
continue
if(!ispath(temp_path, sample.vars[W]))
parse_errors += "list_vars.[W].[W].type is \"path\"; list_vars.[V].[W].value ([temp_list_vars[V][W]["value"]]) is not derived from [sample.vars[W]]"
continue
else if(isicon(sample.vars[W]))
if(temp_list_vars[V][W]["type"] != "icon")
parse_errors += "[sample.type]/[W] is an icon; list_vars.[V].[W].type has been converted to \"icon\""
temp_list_vars[V][W]["type"] = "icon"
if(!istext(temp_list_vars[V][W]["value"]))
parse_errors += "list_vars.[V].[W].type is \"icon\"; list_vars.[V].[W].value ([temp_list_vars[V][W]["value"]]) has been converted to text"
temp_list_vars[V][W]["value"] = "[temp_list_vars[V][W]["value"]]"
if(!fexists(temp_list_vars[V][W]["value"]))
parse_errors += "list_vars.[V].[W].type is \"icon\"; no such file \"[temp_list_vars[V][W]["value"]]\" exists on the server"
continue
else if(istype(sample.vars[W], /datum))
if(temp_list_vars[V][W]["type"] != "ref")
parse_errors += "[sample.type]/[W] is a datum reference; list_vars.[V].[W].type has been converted to \"ref\""
alert = "Reference vars are not assigned on load from file. Be sure to set them correctly."
temp_list_vars[V][W]["type"] = "ref"
temp_list_vars[V][W]["value"] = null
result_list_vars[V][W] = temp_list_vars[V][W]
qdel(sample)
else
result_list_vars[V] = list()
for(var/W in temp_list_vars[V])
if(temp_list_vars[V][W]["flags"])
parse_errors += "list_vars.[V].[W] has unnecessary flags set; they have been unset"
temp_list_vars[V][W]["flags"] = 0
if(temp_list_vars[V][W]["type"] == "list")
if(temp_list_vars[V][W]["value"])
parse_errors += "list_vars.[V].[W].type is \"list\"; list_vars.[V].[W].value ([temp_list_vars[V][W]["value"]]) has been nulled, as it is not used"
temp_list_vars[V][W]["value"] = null
if(!temp_list_vars["[V]/[W]"])
parse_errors += "list_vars.[V].[W].type is \"list\"; there is no property of list_vars whose name is \"[V]/[W]\""
continue
if(temp_list_vars[V][W]["type"] == "num")
if(!isnum(temp_list_vars[V][W]["value"]))
parse_errors += "list_vars.[V].[W].type is \"num\"; list_vars.[V].[W].value ([temp_list_vars[V][W]["value"]]) should be a number"
continue
if(temp_list_vars[V][W]["type"] == "string")
if(!istext(temp_list_vars[V][W]["value"]))
parse_errors += "list_vars.[V].[W].type is \"string\"; list_vars.[V].[W].value ([temp_list_vars[V][W]["value"]]) has been converted to text"
temp_list_vars[V][W]["value"] = "[temp_list_vars[V][W]["value"]]"
if(temp_list_vars[V][W]["type"] == "path")
if(!istext(temp_list_vars[V][W]["value"]))
parse_errors += "list_vars.[V].[W].type is \"path\"; list_vars.[V].[W].value ([temp_list_vars[V][W]["value"]]) has been converted to text"
temp_list_vars[V][W]["value"] = "[temp_list_vars[V][W]["value"]]"
var/temp_path = text2path(temp_list_vars[V][W]["value"])
if(!ispath(temp_path))
parse_errors += "list_vars.[W].[W].type is \"path\"; list_vars.[V].[W].value ([temp_list_vars[V][W]["value"]]) does not correspond to an existing type"
continue
if(temp_list_vars[V][W]["type"] == "icon")
if(!istext(temp_list_vars[V][W]["value"]))
parse_errors += "list_vars.[V].[W].type is \"icon\"; list_vars.[V].[W].value ([temp_list_vars[V][W]["value"]]) has been converted to text"
temp_list_vars[V][W]["value"] = "[temp_list_vars[V][W]["value"]]"
if(!fexists(temp_list_vars[V][W]["value"]))
parse_errors += "list_vars.[V].[W].type is \"icon\"; no such file \"[temp_list_vars[V][W]["value"]]\" exists on the server"
continue
if(temp_list_vars[V][W]["type"] == "ref")
if(!isnull(temp_list_vars[V][W]["value"]))
parse_errors += "list_vars.[V].[W].type is \"ref\"; list_vars.[V].[W].value has been nulled out"
temp_list_vars[V][W]["value"] = null
else
alert = "Reference vars are not assigned on load from file. Be sure to set them correctly."
result_list_vars[V][W] = temp_list_vars[V][W]
qdel(D)
#undef LIST_VAR_FLAGS_TYPED
#undef LIST_VAR_FLAGS_NAMED
/datum/give_sdql_spell/proc/map_var_list(list/L, datum/D)
var/list/ret = list()
for(var/V in L)
if(D.vars[V])
ret["[V] = [string_rep(D.vars[V])]"] = V
return ret
/datum/give_sdql_spell/proc/string_rep(V)
if(istext(V) || isfile(V) || isicon(V))
return "\"[V]\""
else if(isnull(V))
return "null"
else
return "[V]"
/datum/give_sdql_spell/proc/holder_var_validate(V, return_alert = FALSE)
switch(V)
if("bruteloss", "fireloss", "toxloss", "oxyloss", "stun", "knockdown", "paralyze", "unconscious")
if(return_alert)
return ""
return TRUE
else
if(target_mob.vars[V])
if(!isnum(target_mob.vars[V]))
var/new_alert = "[target_mob.type]/[V] is not a number!"
if(return_alert)
return new_alert
alert = new_alert
return FALSE
else
return return_alert ? "" : TRUE
else
var/new_alert = "[target_mob.type] has no such variable [V]!"
if(return_alert)
return new_alert
alert = new_alert
return FALSE
/datum/give_sdql_spell/proc/icon_needs_updating(var_name)
switch(var_name)
if("action_icon", "action_icon_state", "action_background_icon_state")
var/icon/out_icon = icon('icons/effects/effects.dmi', "nothing")
var/image/out_image = image('icons/mob/actions/backgrounds.dmi', null, saved_vars["action_background_icon_state"])
var/overlay_icon = icon(saved_vars["action_icon"], saved_vars["action_icon_state"])
out_image.overlays += image(overlay_icon)
out_icon.Insert(getFlatIcon(out_image, no_anim = TRUE))
action_icon_base64 = icon2base64(out_icon)
if("projectile_var_overrides/icon", "projectile_var_overrides/icon_state")
var/atom/A = /obj/projectile
var/icon = initial(A.icon)
var/icon_state = initial(A.icon_state)
if(list_vars["projectile_var_overrides"]?["icon"])
icon = list_vars["projectile_var_overrides"]["icon"]["value"]
if(list_vars["projectile_var_overrides"]?["icon_state"])
icon_state = list_vars["projectile_var_overrides"]["icon_state"]["value"]
var/icon/out_icon = icon(icon, icon_state, frame = 1)
projectile_icon_base64 = icon2base64(out_icon)
if("hand_var_overrides/icon", "hand_var_overrides/icon_state")
var/atom/A = /obj/item/melee/touch_attack
var/icon = initial(A.icon)
var/icon_state = initial(A.icon_state)
if(list_vars["hand_var_overrides"]?["icon"])
icon = list_vars["hand_var_overrides"]["icon"]["value"]
if(list_vars["hand_var_overrides"]?["icon_state"])
icon_state = list_vars["hand_var_overrides"]["icon_state"]["value"]
var/icon/out_icon = icon(icon, icon_state, frame = 1)
hand_icon_base64 = icon2base64(out_icon)
if("overlay", "overlay_icon", "overlay_icon_state")
var/icon/out_icon = icon(saved_vars["overlay_icon"], saved_vars["overlay_icon_state"], frame = 1)
overlay_icon_base64 = icon2base64(out_icon)
if("ranged_mousepointer")
var/icon/out_icon = icon(saved_vars["ranged_mousepointer"], frame = 1)
mouse_icon_base64 = icon2base64(out_icon)
if("everything")
var/icon/out_icon = icon('icons/effects/effects.dmi', "nothing")
var/image/out_image = image('icons/mob/actions/backgrounds.dmi', null, saved_vars["action_background_icon_state"])
var/overlay_icon = icon(saved_vars["action_icon"], saved_vars["action_icon_state"])
out_image.overlays += image(overlay_icon)
out_icon.Insert(getFlatIcon(out_image, no_anim = TRUE))
action_icon_base64 = icon2base64(out_icon)
if(list_vars["projectile_var_overrides"])
var/atom/A = saved_vars["projectile_type"]
var/icon = initial(A.icon)
var/icon_state = initial(A.icon_state)
if(list_vars["projectile_var_overrides"]?["icon"])
icon = list_vars["projectile_var_overrides"]["icon"]["value"]
if(list_vars["projectile_var_overrides"]?["icon_state"])
icon_state = list_vars["projectile_var_overrides"]["icon_state"]["value"]
out_icon = icon(icon, icon_state, frame = 1)
projectile_icon_base64 = icon2base64(out_icon)
if(list_vars["hand_var_overrides"])
var/atom/A = saved_vars["hand_path"]
var/icon = initial(A.icon)
var/icon_state = initial(A.icon_state)
if(list_vars["hand_var_overrides"]?["icon"])
icon = list_vars["hand_var_overrides"]["icon"]["value"]
if(list_vars["hand_var_overrides"]?["icon_state"])
icon_state = list_vars["hand_var_overrides"]["icon_state"]["value"]
out_icon = icon(icon, icon_state, frame = 1)
hand_icon_base64 = icon2base64(out_icon)
out_icon = icon(saved_vars["overlay_icon"], saved_vars["overlay_icon_state"], frame = 1)
overlay_icon_base64 = icon2base64(out_icon)
out_icon = icon(saved_vars["ranged_mousepointer"], frame = 1)
mouse_icon_base64 = icon2base64(out_icon)
/datum/give_sdql_spell/proc/toggle_list_var(list_name, list_var)
if(list_vars[list_name]?[list_var])
list_vars[list_name][list_var]["value"] = !list_vars[list_name][list_var]["value"]
/datum/give_sdql_spell/proc/set_list_var(list_name, list_var, value)
if(list_vars[list_name]?[list_var])
list_vars[list_name][list_var]["value"] = value
/datum/give_sdql_spell/proc/set_list_ref_var(list_name, list_var)
if(list_vars[list_name]?[list_var])
list_vars[list_name][list_var]["value"] = user.holder?.marked_datum
/datum/give_sdql_spell/proc/rename_list_var(list_name, list_var, new_name)
if(!new_name)
alert = "You can't give a list variable an empty string for a name!"
return
if(list_var == new_name)
return
if(list_vars[list_name])
var/list/L = list_vars[list_name]
var/ind = L.Find(list_var)
if(ind)
if(list_vars[list_name][new_name])
alert = "There is already a variable named [new_name] in [list_name]!"
else
var/old_val = list_vars[list_name][list_var]
list_vars[list_name][ind] = new_name
list_vars[list_name][new_name] = old_val
/datum/give_sdql_spell/proc/change_list_var_type(list_name, list_var, var_type)
if(list_vars[list_name]?[list_var])
if(list_vars[list_name][list_var]["type"] == "list" && var_type != "list")
purge_list_var("[list_name]/[list_var]")
list_vars[list_name][list_var]["type"] = var_type
switch(var_type)
if("string", "path")
list_vars[list_name][list_var]["value"] = ""
if("bool", "num")
list_vars[list_name][list_var]["value"] = 0
if("list")
list_vars[list_name][list_var]["value"] = null
list_vars |= list("[list_name]/[list_var]" = list())
/datum/give_sdql_spell/proc/remove_list_var(list_name, list_var)
if(list_vars[list_name])
var/list/L = list_vars[list_name]
var/ind = L.Find(list_var)
if(ind)
if(list_vars[list_name][list_var]["type"] == "list")
purge_list_var("[list_name]/[list_var]")
L.Cut(ind, ind+1)
list_vars[list_name] = L
/datum/give_sdql_spell/proc/purge_list_var(list_name)
var/ind = list_vars.Find(list_name)
if(ind)
for(var/V in list_vars[list_name])
if(list_vars[list_name][V]["type"] == "list")
purge_list_var("[list_name]/[V]")
list_vars.Cut(ind, ind+1)
/datum/give_sdql_spell/proc/generate_list_var(list_name)
if(!list_vars[list_name])
return null
var/list/ret = list()
for(var/V in list_vars[list_name])
if(list_vars[list_name][V]["type"] == "list")
ret[V] = generate_list_var("[list_name]/[V]")
else if(list_vars[list_name][V]["type"] == "path")
ret[V] = text2path(list_vars[list_name][V]["value"])
else if(list_vars[list_name][V]["type"] == "icon")
ret[V] = icon(list_vars[list_name][V]["value"])
else
ret[V] = list_vars[list_name][V]["value"]
return ret
/datum/give_sdql_spell/proc/give_spell()
var/path = text2path("/obj/effect/proc_holder/spell/[spell_type]/sdql")
var/obj/effect/proc_holder/spell/new_spell = new path(null, target_mob, user.ckey)
GLOB.sdql_spells += new_spell
reassign_vars(new_spell)
new_spell.action.UpdateButtons()
if(target_mob.mind)
target_mob.mind.AddSpell(new_spell)
else
target_mob.AddSpell(new_spell)
to_chat(user, span_danger("Spells given to mindless mobs will not be transferred in mindswap or cloning!"))
return new_spell
/datum/give_sdql_spell/proc/reassign_vars(obj/effect/proc_holder/spell/target)
if(!target)
CRASH("edit_spell must be called with a non_null target")
var/datum/component/sdql_executor/executor = target.GetComponent(/datum/component/sdql_executor)
if(!executor)
CRASH("[src]'s SDQL executor component went missing!")
for(var/V in saved_vars+list_vars)
if(V == "query")
executor.vv_edit_var("query", saved_vars["query"])
else if(V == "suppress_message_admins")
executor.vv_edit_var("suppress_message_admins", saved_vars["suppress_message_admins"])
else if(V == "scratchpad")
var/list/new_scratchpad = generate_list_var("scratchpad")
if(new_scratchpad)
executor.vv_edit_var("scratchpad", new_scratchpad)
else if(target.vars[V])
if(islist(target.vars[V]))
if(special_list_vars[V])
var/list/overrides_to_save = list_vars[V]
executor.saved_overrides[V] = overrides_to_save.Copy()
var/list/list_var = generate_list_var(V)
if(list_var)
target.vv_edit_var(V, list_var)
else if(isicon(target.vars[V]))
target.vv_edit_var(V, icon(saved_vars[V]))
else
target.vv_edit_var(V, saved_vars[V])
@@ -1,148 +0,0 @@
/obj/effect/proc_holder/spell/aimed/sdql
name = "Aimed SDQL Spell"
desc = "If you are reading this outside of the \"Give SDQL Spell\" menu, tell the admin that gave this spell to you to use said menu."
projectile_type = /obj/projectile
/obj/effect/proc_holder/spell/aimed/sdql/Initialize(mapload, new_owner, giver)
. = ..()
AddComponent(/datum/component/sdql_executor, giver)
RegisterSignal(src, COMSIG_PROJECTILE_ON_HIT, .proc/on_projectile_hit)
/obj/effect/proc_holder/spell/aimed/sdql/proc/on_projectile_hit(source, firer, target)
SIGNAL_HANDLER
var/datum/component/sdql_executor/executor = GetComponent(/datum/component/sdql_executor)
if(!executor)
CRASH("[src]'s SDQL executor component went missing!")
INVOKE_ASYNC(executor, /datum/component/sdql_executor/proc/execute, list(target), owner.resolve())
/obj/effect/proc_holder/spell/aoe_turf/sdql
name = "AoE SDQL Spell"
desc = "If you are reading this outside of the \"Give SDQL Spell\" menu, tell the admin that gave this spell to you to use said menu."
/obj/effect/proc_holder/spell/aoe_turf/sdql/Initialize(mapload, new_owner, giver)
. = ..()
AddComponent(/datum/component/sdql_executor, giver)
/obj/effect/proc_holder/spell/aoe_turf/sdql/cast(list/targets, mob/user)
var/datum/component/sdql_executor/executor = GetComponent(/datum/component/sdql_executor)
if(!executor)
CRASH("[src]'s SDQL executor component went missing!")
executor.execute(targets, user)
/obj/effect/proc_holder/spell/cone/sdql
name = "Cone SDQL Spell"
desc = "If you are reading this outside of the \"Give SDQL Spell\" menu, tell the admin that gave this spell to you to use said menu."
var/list/targets = list()
/obj/effect/proc_holder/spell/cone/sdql/Initialize(mapload, new_owner, giver)
. = ..()
AddComponent(/datum/component/sdql_executor, giver)
/obj/effect/proc_holder/spell/cone/sdql/do_mob_cone_effect(mob/living/target_mob, level)
targets |= target_mob
/obj/effect/proc_holder/spell/cone/sdql/do_obj_cone_effect(obj/target_obj, level)
targets |= target_obj
/obj/effect/proc_holder/spell/cone/sdql/do_turf_cone_effect(turf/target_turf, level)
targets |= target_turf
/obj/effect/proc_holder/spell/cone/sdql/cast(list/targets, mob/user)
. = ..()
var/datum/component/sdql_executor/executor = GetComponent(/datum/component/sdql_executor)
if(!executor)
CRASH("[src]'s SDQL executor component went missing!")
executor.execute(targets, user)
targets = list()
/obj/effect/proc_holder/spell/cone/staggered/sdql
name = "Staggered Cone SDQL Spell"
desc = "If you are reading this outside of the \"Give SDQL Spell\" menu, tell the admin that gave this spell to you to use said menu."
var/list/targets = list()
/obj/effect/proc_holder/spell/cone/staggered/sdql/Initialize(mapload, new_owner, giver)
. = ..()
AddComponent(/datum/component/sdql_executor, giver)
/obj/effect/proc_holder/spell/cone/staggered/sdql/do_mob_cone_effect(mob/living/target_mob, level)
targets |= target_mob
/obj/effect/proc_holder/spell/cone/staggered/sdql/do_obj_cone_effect(obj/target_obj, level)
targets |= target_obj
/obj/effect/proc_holder/spell/cone/staggered/sdql/do_turf_cone_effect(turf/target_turf, level)
targets |= target_turf
/obj/effect/proc_holder/spell/cone/staggered/sdql/do_cone_effects(list/target_turf_list, level)
. = ..()
var/datum/component/sdql_executor/executor = GetComponent(/datum/component/sdql_executor)
if(!executor)
CRASH("[src]'s SDQL executor component went missing!")
executor.execute(target_turf_list, owner.resolve())
targets = list()
/obj/effect/proc_holder/spell/pointed/sdql
name = "Pointed SDQL Spell"
desc = "If you are reading this outside of the \"Give SDQL Spell\" menu, tell the admin that gave this spell to you to use said menu."
/obj/effect/proc_holder/spell/pointed/sdql/Initialize(mapload, new_owner, giver)
. = ..()
AddComponent(/datum/component/sdql_executor, giver)
/obj/effect/proc_holder/spell/pointed/sdql/cast(list/targets, mob/user)
var/datum/component/sdql_executor/executor = GetComponent(/datum/component/sdql_executor)
if(!executor)
CRASH("[src]'s SDQL executor component went missing!")
executor.execute(targets, user)
/obj/effect/proc_holder/spell/self/sdql
name = "Self SDQL Spell"
desc = "If you are reading this outside of the \"Give SDQL Spell\" menu, tell the admin that gave this spell to you to use said menu."
/obj/effect/proc_holder/spell/self/sdql/Initialize(mapload, new_owner, giver)
. = ..()
AddComponent(/datum/component/sdql_executor, giver)
/obj/effect/proc_holder/spell/self/sdql/cast(list/targets, mob/user)
var/datum/component/sdql_executor/executor = GetComponent(/datum/component/sdql_executor)
if(!executor)
CRASH("[src]'s SDQL executor component went missing!")
executor.execute(targets, user)
/obj/effect/proc_holder/spell/targeted/sdql
name = "Targeted SDQL Spell"
desc = "If you are reading this outside of the \"Give SDQL Spell\" menu, tell the admin that gave this spell to you to use said menu."
/obj/effect/proc_holder/spell/targeted/sdql/Initialize(mapload, new_owner, giver)
. = ..()
AddComponent(/datum/component/sdql_executor, giver)
/obj/effect/proc_holder/spell/targeted/sdql/cast(list/targets, mob/user)
var/datum/component/sdql_executor/executor = GetComponent(/datum/component/sdql_executor)
if(!executor)
CRASH("[src]'s SDQL executor component went missing!")
executor.execute(targets, user)
/obj/effect/proc_holder/spell/targeted/touch/sdql
name = "Touch SDQL Spell"
desc = "If you are reading this outside of the \"Give SDQL Spell\" menu, tell the admin that gave this spell to you to use said menu."
var/list/hand_var_overrides = list() //The touch attack has its vars changed to the ones put in this list.
/obj/effect/proc_holder/spell/targeted/touch/sdql/Initialize(mapload, new_owner, giver)
. = ..()
AddComponent(/datum/component/sdql_executor, giver)
/obj/effect/proc_holder/spell/targeted/touch/sdql/ChargeHand(mob/living/carbon/user)
if(..())
for(var/V in hand_var_overrides)
if(attached_hand.vars[V])
attached_hand.vv_edit_var(V, hand_var_overrides[V])
RegisterSignal(attached_hand, COMSIG_ITEM_AFTERATTACK, .proc/on_touch_attack)
user.update_inv_hands()
/obj/effect/proc_holder/spell/targeted/touch/sdql/proc/on_touch_attack(source, target, user)
SIGNAL_HANDLER
var/datum/component/sdql_executor/executor = GetComponent(/datum/component/sdql_executor)
if(!executor)
CRASH("[src]'s SDQL executor component went missing!")
INVOKE_ASYNC(executor, /datum/component/sdql_executor/proc/execute, list(target), user)