diff --git a/code/datums/mutations/actions.dm b/code/datums/mutations/actions.dm index 0af9fe6d2cc..d984dd9f24f 100644 --- a/code/datums/mutations/actions.dm +++ b/code/datums/mutations/actions.dm @@ -140,6 +140,7 @@ return FALSE /obj/effect/proc_holder/spell/aimed/firebreath/ready_projectile(obj/projectile/P, atom/target, mob/user, iteration) + . = ..() if(!istype(P, /obj/projectile/magic/aoe/fireball)) return var/obj/projectile/magic/aoe/fireball/F = P diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 5ca460568fc..d3fa66c3cbf 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -90,6 +90,7 @@ GLOBAL_PROTECT(admin_verbs_admin) /datum/admins/proc/togglesooc, //SKYRAT EDIT ADDITION - ADMIN /datum/admins/proc/open_borgopanel, /datum/admins/proc/view_all_circuits, + /datum/admins/proc/view_all_sdql_spells, ) GLOBAL_LIST_INIT(admin_verbs_ban, list(/client/proc/unban_panel, /client/proc/ban_panel, /client/proc/stickybanpanel)) GLOBAL_PROTECT(admin_verbs_ban) @@ -204,7 +205,7 @@ GLOBAL_PROTECT(admin_verbs_debug) /datum/admins/proc/create_or_modify_area, /client/proc/check_timer_sources, /client/proc/toggle_cdn, - /client/proc/cmd_give_sdql_spell, + /client/proc/cmd_sdql_spell_menu, /client/proc/adventure_manager, /client/proc/load_circuit, ) diff --git a/code/modules/admin/verbs/SDQL2/SDQL_spells/executor_component.dm b/code/modules/admin/verbs/SDQL2/SDQL_spells/executor_component.dm new file mode 100644 index 00000000000..f358375dac7 --- /dev/null +++ b/code/modules/admin/verbs/SDQL2/SDQL_spells/executor_component.dm @@ -0,0 +1,51 @@ +/datum/component/sdql_executor + var/query = "CALL visible_message(\"The spell fizzles!\") ON * IN TARGETS" + var/giver //The ckey of the user that gave this spell + 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) ? "\[[targets_and_user_list.len] items]" : 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) ? "\[[targets.len] items]" : 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])") + 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 diff --git a/code/modules/admin/verbs/SDQL2/SDQL_spells/spell_admin_panel.dm b/code/modules/admin/verbs/SDQL2/SDQL_spells/spell_admin_panel.dm new file mode 100644 index 00000000000..1469982f2b6 --- /dev/null +++ b/code/modules/admin/verbs/SDQL2/SDQL_spells/spell_admin_panel.dm @@ -0,0 +1,77 @@ +/// 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() diff --git a/code/modules/admin/verbs/SDQL2/SDQL_spells/spell_edit_menu.dm b/code/modules/admin/verbs/SDQL2/SDQL_spells/spell_edit_menu.dm new file mode 100644 index 00000000000..609d6a742d7 --- /dev/null +++ b/code/modules/admin/verbs/SDQL2/SDQL_spells/spell_edit_menu.dm @@ -0,0 +1,921 @@ +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" = "") + 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", + "cult_req", + "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("none", "whisper", "shout", "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.", + "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.", + "cult_req" = "Whether the user has to be wearing cult 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 \"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 + 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(new_path) + 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", sortList(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.UpdateButtonIcon() + 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 = deepCopyList(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 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 & ~(LIST_VAR_FLAGS_NAMED | LIST_VAR_FLAGS_NAMED)) + 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_NAMED + 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.UpdateButtonIcon() + 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 == "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]) diff --git a/code/modules/admin/verbs/SDQL2/SDQL_spells/spells.dm b/code/modules/admin/verbs/SDQL2/SDQL_spells/spells.dm new file mode 100644 index 00000000000..7716b0b07c6 --- /dev/null +++ b/code/modules/admin/verbs/SDQL2/SDQL_spells/spells.dm @@ -0,0 +1,146 @@ +/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) + var/datum/component/sdql_executor/executor = GetComponent(/datum/component/sdql_executor) + if(!executor) + CRASH("[src]'s SDQL executor component went missing!") + executor.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) + var/datum/component/sdql_executor/executor = GetComponent(/datum/component/sdql_executor) + if(!executor) + CRASH("[src]'s SDQL executor component went missing!") + executor.execute(list(target), user) diff --git a/code/modules/admin/verbs/SDQL2/SDQL_spells_and_items.dm b/code/modules/admin/verbs/SDQL2/SDQL_spells_and_items.dm deleted file mode 100644 index 882c8e25a97..00000000000 --- a/code/modules/admin/verbs/SDQL2/SDQL_spells_and_items.dm +++ /dev/null @@ -1,848 +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." - var/query = "CALL visible_message(\"The spell fizzles!\") ON * IN TARGETS" - var/list/scratchpad = list() //Use this to store vars in between queries and casts. - projectile_type = /obj/projectile/sdql - -/obj/effect/proc_holder/spell/aimed/sdql/ready_projectile(obj/projectile/P, atom/target, mob/user, iteration) - var/obj/projectile/sdql/S = P - S.linked_spell = src - S.query = query - -/obj/projectile/sdql - name = "\improper SDQL projectile" - damage_type = STAMINA - nodamage = TRUE - damage = 0 - var/query - var/obj/effect/proc_holder/spell/linked_spell - -/obj/projectile/sdql/on_hit(atom/target, blocked, pierce_hit) - . = ..() - var/mob/firer_mob = firer - process_spell_query(query, list(target), firer_mob, linked_spell) - -/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." - var/query = "CALL visible_message(\"The spell fizzles!\") ON * IN TARGETS" - var/list/scratchpad = list() //Use this to store vars in between queries and casts. - -/obj/effect/proc_holder/spell/aoe_turf/sdql/cast(list/targets, mob/user) - process_spell_query(query, targets, user, src) - -/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/query = "CALL visible_message(\"The spell fizzles!\") ON * IN TARGETS" - var/list/targets = list() - var/list/scratchpad = list() //Use this to store vars in between queries and casts. - -/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) - . = ..() - process_spell_query(query, targets, user, src) - 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/query = "CALL visible_message(\"The spell fizzles!\") ON * IN TARGETS" - var/list/targets = list() - var/list/scratchpad = list() //Use this to store vars in between queries and casts. - -/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) - . = ..() - process_spell_query(query, targets, usr, src) - 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." - var/query = "CALL visible_message(\"The spell fizzles!\") ON * IN TARGETS" - var/list/scratchpad = list() //Use this to store vars in between queries and casts. - -/obj/effect/proc_holder/spell/pointed/sdql/cast(list/targets, mob/user) - process_spell_query(query, targets, user, src) - -/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." - var/query = "CALL visible_message(\"The spell fizzles!\") ON * IN TARGETS" - var/list/scratchpad = list() //Use this to store vars in between queries and casts. - -/obj/effect/proc_holder/spell/self/sdql/cast(list/targets, mob/user) - process_spell_query(query, list(user), user, src) - -/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." - var/query = "CALL visible_message(\"The spell fizzles!\") ON * IN TARGETS" - var/list/scratchpad = list() //Use this to store vars in between queries and casts. - -/obj/effect/proc_holder/spell/targeted/sdql/cast(list/targets, mob/user) - process_spell_query(query, targets, user, src) - -/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." - hand_path = /obj/item/melee/touch_attack/sdql - var/query = "CALL visible_message(\"The spell fizzles!\") ON * IN TARGETS" - var/list/scratchpad = list() //Use this to store vars in between queries and casts. - 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/ChargeHand(mob/living/carbon/user) - if(..()) - for(var/V in hand_var_overrides) - if(V in attached_hand.vars) - attached_hand.vv_edit_var(V, hand_var_overrides[V]) - user.update_inv_hands() - - -/obj/item/melee/touch_attack/sdql - name = "\improper SDQL touch attack" - 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." - catchphrase = "ADMINS WERE LAZY!!" - -/obj/item/melee/touch_attack/sdql/afterattack(atom/target, mob/user, proximity) - var/obj/effect/proc_holder/spell/targeted/touch/sdql/spell = attached_spell - process_spell_query(spell.query, list(target), user, spell) - . = ..() - -//Returns the address of x without the square brackets around it. -#define RAW_ADDRESS(x) copytext("\ref[x]",2,-1) - -/proc/process_spell_query(query_text, list/targets, mob/user, source) - if(!CONFIG_GET(flag/sdql_spells)) - return - if(!length(query_text)) - return - var/message_query = query_text - var/list/targets_and_user_list = targets+user - 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) ? "\[[targets_and_user_list.len] items]" : 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) ? "\[[targets.len] items]" : targets_string) - query_text = replacetextEx_char(query_text, "SOURCE", "{[RAW_ADDRESS(source)]}") - message_query = replacetextEx_char(message_query, "SOURCE", "{[RAW_ADDRESS(source)]}") - var/query_message = "[key_name(user)] executed SDQL query(s): \"[message_query]\" using a player query caller." - message_admins(query_message) - log_game(query_message) - var/list/query_list = SDQL2_tokenize(query_text) - if(!query_list.len) - return - var/list/querys = SDQL_parse(query_list) - if(!querys.len) - return - - var/list/datum/sdql2_query/running = list() - var/list/datum/sdql2_query/waiting_queue = list() //Sequential queries queue. - - for(var/list/query_tree in querys) - var/datum/sdql2_query/query = new /datum/sdql2_query(query_tree, SU = TRUE, admin_interact = FALSE) - if(QDELETED(query)) - continue - waiting_queue += query - - var/datum/sdql2_query/query = popleft(waiting_queue) - running += query - query.ARun() - - var/finished = FALSE - do - CHECK_TICK - finished = TRUE - for(var/i in running) - query = i - if(QDELETED(query)) - running -= query - continue - else if(query.state != SDQL2_STATE_IDLE) - finished = FALSE - if(query.state == SDQL2_STATE_ERROR) - running -= query - else - if(query.finished) - qdel(query) - if(waiting_queue.len) - finished = FALSE - var/datum/sdql2_query/next_query = popleft(waiting_queue) - running += next_query - next_query.ARun() - else - running -= query - while(!finished) - -/proc/ref_list(list/L) - if(!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 - -/client/proc/cmd_give_sdql_spell(mob/target in GLOB.mob_list) - set name = "Give 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/spell_type - var/list/saved_vars = list() - var/list/list_vars = list("scratchpad" = list()) - 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", - "cult_req", - "deactive_msg", - "desc", - "drawmessage", - "dropmessage", - "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_amount", - "projectile_var_overrides", - "projectiles_per_fire", - "query", - "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_message", - "target_ignore_prev", - ) - - //If a spell creates a datum with vars it overrides, this list should contain an association with the supertype of the created datum - var/static/list/special_list_vars = list( - "projectile_var_overrides" = list( - "supertype" = /obj/projectile, - "type" = /obj/projectile/sdql, - ), - "hand_var_overrides" = list( - "supertype" = /obj/item/melee/touch_attack, - "type" = /obj/item/melee/touch_attack/sdql, - ), - ) - - var/static/list/static_data - - //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 - user = CLIENT_FROM_VAR(_user) - - if(!isliving(target)) - alert("Invalid mob") - return - target_mob = target - -/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) - -/datum/give_sdql_spell/ui_data(mob/user, params) - var/list/data = list() - data["type"] = spell_type - data["saved_vars"] = saved_vars - data["list_vars"] = list_vars - 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) - if(!static_data) - static_data = 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\ - You can use the list variable \"scratchpad\" to store variables between individual queries within the same cast or between multiple casts.", - "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 replaced with a list containing a reference to the caster.", - "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.", - "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.", - "cult_req" = "Whether the user has to be wearing cult 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 \"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 for 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.", - ), - ) - return static_data - -#define LIST_VAR_FLAGS_TYPED 1 -#define LIST_VAR_FLAGS_NAMED 2 - -/datum/give_sdql_spell/ui_act(action, params, datum/tgui/ui) - if(..()) - return - . = TRUE - switch(action) - if("type") - spell_type = params["path"] - var/path = text2path("/obj/effect/proc_holder/spell/[spell_type]/sdql") - var/datum/sample = new path - if(spell_type) - for(var/V in sample.vars) - if(V in editable_spell_vars) - if(islist(sample.vars[V])) - list_vars += list("[V]" = list()) - if(V in special_list_vars) - var/subpath = special_list_vars[V]["type"] - var/datum/subsample = new subpath - if("icon" in sample.vars) - icon_needs_updating("[V]/icon") - qdel(subsample) - else - saved_vars[V] = sample.vars[V] - icon_needs_updating(V) - qdel(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("list_variable_add") - if(params["list"] in list_vars) - if(params["list"] in special_list_vars) - var/superpath = special_list_vars[params["list"]]["supertype"] - var/path = special_list_vars[params["list"]]["type"] - var/datum/supersample = new superpath - var/datum/sample = new path - var/list/choosable_vars = map_var_list((sample.vars&supersample.vars)-list_vars[params["list"]], sample) - var/chosen_var = input(user, "Select variable to add.", "Add SDQL Spell", null) as null|anything in sortList(choosable_vars) - if(chosen_var) - if(islist(sample.vars[choosable_vars[chosen_var]])) - list_vars[params["list"]] += list("[choosable_vars[chosen_var]]" = list("type" = "list", "value" = null, "flags" = LIST_VAR_FLAGS_TYPED|LIST_VAR_FLAGS_NAMED)) - list_vars |= list("[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(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) - qdel(supersample) - else - if(!("new_var" in list_vars[params["list"]])) - 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("save") - var/f = file("data/TempSpellUpload") - fdel(f) - WRITE_FILE(f, json_encode(list("type" = spell_type, "vars" = saved_vars, "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 - if(load_from_json(json)) - icon_needs_updating("everything") - else - alert = "Malformed/Outdated file!" - return - if("confirm") - give_spell() - ui.close() - -/datum/give_sdql_spell/proc/load_from_json(json) - if(!(("type" in json) && ("vars" in json) && ("list_vars" in json))) - return FALSE - var/temp_type = json["type"] - var/datum/D = text2path("/obj/effect/proc_holder/spell/[temp_type]/sdql") - if(!ispath(D)) - return FALSE - if(!islist(json["vars"])) - return FALSE - if(!islist(json["list_vars"])) - return FALSE - var/list/temp_vars = json["vars"] - var/list/temp_list_vars = json["list_vars"] - D = new D - . = TRUE - for(var/V in temp_vars) - if(!istext(V)) - . = FALSE - break - if(!(V in editable_spell_vars)) - . = FALSE - break - if(!(V in D.vars)) - . = FALSE - break - if(islist(D.vars[V])) - . = FALSE - break - if(istext(D.vars[V]) || isicon(D.vars[V]) || ispath(D.vars[V])) - if(!istext(temp_vars[V])) - . = FALSE - break - if(isnum(D.vars[V])) - if(!isnum(temp_vars[V])) - . = FALSE - break - if(.) - for(var/V in temp_list_vars) - if(!islist(temp_list_vars[V])) - . = FALSE - break - if((V in special_list_vars) && (V in D.vars)) - var/datum/sample = special_list_vars[V]["type"] - sample = new sample - for(var/W in temp_list_vars[V]) - if(!istext(W)) - . = FALSE - break - if(!islist(temp_list_vars[V][W])) - . = FALSE - break - if(!(("type" in temp_list_vars[V][W]) && ("value" in temp_list_vars[V][W]) && ("flags" in temp_list_vars[V][W]))) - . = FALSE - break - if(!isnum(temp_list_vars[V][W]["flags"]) || (temp_list_vars[V][W]["flags"] & LIST_VAR_FLAGS_TYPED|LIST_VAR_FLAGS_NAMED) == LIST_VAR_FLAGS_TYPED) - . = FALSE - break - if(!istext(temp_list_vars[V][W]["type"])) - . = FALSE - break - if(!(temp_list_vars[V][W]["flags"] & LIST_VAR_FLAGS_TYPED)) - if(!isnull(sample.vars[W])) - . = FALSE - break - else - switch(temp_list_vars[V][W]["type"]) - if("list") - if(!islist(sample.vars[W])) - . = FALSE - break - if(!("[V]/[W]" in temp_list_vars)) - . = FALSE - break - if("num") - if(isnum(temp_list_vars[V][W]["value"])) - if(!(isnum(sample.vars[W]))) - . = FALSE - break - else - . = FALSE - break - if("string") - if(istext(temp_list_vars[V][W]["value"])) - if(!(istext(sample.vars[W]) || isfile(sample.vars[W]))) - . = FALSE - break - else - . = FALSE - break - if("path") - if(istext(temp_list_vars[V][W]["value"])) - if(!(ispath(sample.vars[W]))) - . = FALSE - break - else - . = FALSE - break - if("icon") - if(istext(temp_list_vars[V][W]["value"])) - if(!(isicon(sample.vars[W]))) - . = FALSE - break - else - . = FALSE - break - qdel(sample) - if(!.) - break - qdel(D) - if(.) - spell_type = temp_type - saved_vars = temp_vars - list_vars = temp_list_vars - -#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(V in D.vars) - 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) - switch(V) - if("bruteloss", "fireloss", "toxloss", "oxyloss", "stun", "knockdown", "paralyze", "unconscious") - return TRUE - else - if(V in target_mob.vars) - if(!isnum(target_mob.vars[V])) - alert = "[target_mob.type]/[V] is not a number!" - return FALSE - else - return TRUE - else - alert = "[target_mob.type] has no such variable [V]!" - 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/sdql - 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/sdql - 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 = /obj/projectile/sdql - 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 = /obj/item/melee/touch_attack/sdql - 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_name in list_vars) - if(list_var in list_vars[list_name]) - 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_name in list_vars) - if(list_var in list_vars[list_name]) - list_vars[list_name][list_var]["value"] = value - -/datum/give_sdql_spell/proc/rename_list_var(list_name, list_var, new_name) - if(list_var == new_name) - return - if(list_name in list_vars) - var/list/L = list_vars[list_name] - var/ind = L.Find(list_var) - if(ind) - if(new_name in list_vars[list_name]) - alert = "There is already a variable named [new_name] in [list_name]!" - else - list_vars[list_name][ind] = new_name - -/datum/give_sdql_spell/proc/change_list_var_type(list_name, list_var, var_type) - if(list_name in list_vars) - if(list_var in list_vars[list_name]) - 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_name in list_vars) - 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_name in list_vars)) - 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 - for(var/V in saved_vars+list_vars) - if(V in new_spell.vars) - if(islist(new_spell.vars[V])) - var/list/list_var = generate_list_var(V) - if(list_var) - new_spell.vv_edit_var(V, list_var) - else if(isicon(new_spell.vars[V])) - new_spell.vv_edit_var(V, icon(saved_vars[V])) - else - new_spell.vv_edit_var(V, saved_vars[V]) - - //delete and recreate the action so the overriden vars are respected by the action button - qdel(new_spell.action) - new_spell.action = new new_spell.base_action(new_spell) - 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!")) diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index ff6cc1f4df3..34c632b7f19 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -1254,7 +1254,7 @@ if(href_list[VV_HK_SDQL_SPELL]) if(!check_rights(R_DEBUG)) return - usr.client.cmd_give_sdql_spell(src) + usr.client.cmd_sdql_spell_menu(src) /** * extra var handling for the logging var */ diff --git a/code/modules/spells/spell_types/aimed.dm b/code/modules/spells/spell_types/aimed.dm index f40f19b8c60..8e35be95735 100644 --- a/code/modules/spells/spell_types/aimed.dm +++ b/code/modules/spells/spell_types/aimed.dm @@ -88,6 +88,7 @@ return TRUE /obj/effect/proc_holder/spell/aimed/proc/ready_projectile(obj/projectile/P, atom/target, mob/user, iteration) + P.fired_from = src return /obj/effect/proc_holder/spell/aimed/lightningbolt @@ -172,6 +173,7 @@ QDEL_NULL(lockon_component) /obj/effect/proc_holder/spell/aimed/spell_cards/ready_projectile(obj/projectile/P, atom/target, mob/user, iteration) + . = ..() if(current_target_weakref) var/atom/A = current_target_weakref.resolve() if(A && get_dist(A, user) < 7) diff --git a/tgstation.dme b/tgstation.dme index 17baa936f90..834a1523507 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1698,7 +1698,10 @@ #include "code\modules\admin\verbs\SDQL2\SDQL_2.dm" #include "code\modules\admin\verbs\SDQL2\SDQL_2_parser.dm" #include "code\modules\admin\verbs\SDQL2\SDQL_2_wrappers.dm" -#include "code\modules\admin\verbs\SDQL2\SDQL_spells_and_items.dm" +#include "code\modules\admin\verbs\SDQL2\SDQL_spells\executor_component.dm" +#include "code\modules\admin\verbs\SDQL2\SDQL_spells\spell_admin_panel.dm" +#include "code\modules\admin\verbs\SDQL2\SDQL_spells\spell_edit_menu.dm" +#include "code\modules\admin\verbs\SDQL2\SDQL_spells\spells.dm" #include "code\modules\admin\view_variables\admin_delete.dm" #include "code\modules\admin\view_variables\debug_variables.dm" #include "code\modules\admin\view_variables\filterrific.dm" diff --git a/tgui/packages/tgui/interfaces/SDQLSpellAdminPanel.tsx b/tgui/packages/tgui/interfaces/SDQLSpellAdminPanel.tsx new file mode 100644 index 00000000000..b66e2afde5b --- /dev/null +++ b/tgui/packages/tgui/interfaces/SDQLSpellAdminPanel.tsx @@ -0,0 +1,88 @@ +import { BooleanLike } from "common/react"; +import { useBackend } from "../backend"; +import { Button, Table } from "../components"; +import { Window } from "../layouts"; + +type SDQLSpellAdminPanelData = { + spells: { + ref: string; + name: string; + ownerRef: string; + owner: string; + creator: string; + }[] +} + +export const SDQLSpellAdminPanel = (props, context) => { + const { act, data } = useBackend(context); + + return ( + + + + + + Spell Name + + + + Spell Owner + + + + Spell Creator + + + + Actions + + + + {data.spells.map(spell => { + const createSpellAct = (action: string) => () => { + act(action, { spell: spell.ref }); + }; + + const createOwnerAct = (action: string) => () => { + act(action, { owner: spell.ownerRef }); + }; + + return ( + + + {spell.name} + + + + {spell.owner} + + + + {spell.creator} + + + + + + + + + + + + + ); + })} +
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/SDQLSpellMenu.js b/tgui/packages/tgui/interfaces/SDQLSpellMenu.js index 28984716330..4e326925fef 100644 --- a/tgui/packages/tgui/interfaces/SDQLSpellMenu.js +++ b/tgui/packages/tgui/interfaces/SDQLSpellMenu.js @@ -1,5 +1,5 @@ import { useBackend } from '../backend'; -import { Box, Button, Collapsible, Dropdown, Input, NumberInput, Section, Stack, Tooltip } from '../components'; +import { Box, Button, Collapsible, Dropdown, Input, LabeledList, Modal, NumberInput, Section, Stack, Tooltip } from '../components'; import { Window } from '../layouts'; /** @@ -38,7 +38,7 @@ const typevars = (type) => { }, { name: 'charge_max', type: 'int', options: null, default_value: 100 }, { - name: 'still_recharging_message', + name: 'still_recharging_msg', type: 'string', options: null, default_value: '', @@ -231,6 +231,12 @@ const typevars = (type) => { options: null, default_value: '', }, + { + name: 'projectile_type', + type: 'path', + options: null, + default_value: '/obj/projectile', + }, { name: 'projectile_amount', type: 'int', @@ -247,7 +253,7 @@ const typevars = (type) => { name: 'projectile_var_overrides', type: 'list', options: null, - default_value: [], + default_value: {}, } ); break; @@ -335,11 +341,17 @@ const typevars = (type) => { options: null, default_value: '', }, + { + name: 'hand_path', + type: 'path', + options: null, + default_value: '/obj/item/melee/touch_attack', + }, { name: 'hand_var_overrides', type: 'list', options: null, - default_value: [], + default_value: {}, } ); break; @@ -357,7 +369,7 @@ const typevars = (type) => { export const SDQLSpellMenu = (props, context) => { const { act, data } = useBackend(context); - const { type, types, alert } = data; + const { type, types, fixed_type, alert, parse_errors } = data; return ( @@ -369,6 +381,7 @@ export const SDQLSpellMenu = (props, context) => { act('type', { path: value })} /> @@ -408,6 +421,7 @@ export const SDQLSpellMenu = (props, context) => { + {parse_errors && } ); }; @@ -472,6 +486,8 @@ const WrapInTooltip = (props, context) => { {children} ); + } else { + return children; } }; @@ -559,8 +575,15 @@ const SDQLSpellInput = (props, context) => { act('variable', { name, value: options.indexOf(value) })} /> ); + case 'path': + return ( + + + + + ); +}; + +const SDQLSpellParsedList = (props, context) => { + const { list, name, recursive_props } = props; + const { act, data } = useBackend(context); + const { parsed_list_vars } = data; + return ( + + + + {Object.entries(parsed_list_vars[list]) + .sort(([, { type_1 }], [, { type_2 }]) => { + return (type_1 === "list" ? 1 : 0) - (type_2 === "list" ? 1 : 0); + }).map(([name, { type, value }]) => { + return type === "list" ? ( + + ) : ( + + {value} + + ); + })} + + + + ); +};