diff --git a/code/modules/eventkit/event_machinery.dm b/code/modules/eventkit/event_machinery.dm
new file mode 100644
index 0000000000..be65102625
--- /dev/null
+++ b/code/modules/eventkit/event_machinery.dm
@@ -0,0 +1,3 @@
+/obj/machinery/eventkit/custom
+ name = "Custom Machine"
+ desc = "A custom machine created by a GM."
diff --git a/code/modules/eventkit/gm_interfaces/mob_spawner.dm b/code/modules/eventkit/gm_interfaces/mob_spawner.dm
new file mode 100644
index 0000000000..181d5abba6
--- /dev/null
+++ b/code/modules/eventkit/gm_interfaces/mob_spawner.dm
@@ -0,0 +1,132 @@
+/datum/eventkit/mob_spawner
+ // The path of the mob to be spawned
+ var/path
+
+ // Defines if the location of the spawned mob should be bound of the users position
+ var/loc_lock = FALSE
+
+/datum/eventkit/mob_spawner/New()
+ . = ..()
+
+/datum/eventkit/mob_spawner/tgui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "MobSpawner", "EventKit - Mob Spawner")
+ ui.set_autoupdate(FALSE)
+ ui.open()
+
+/datum/eventkit/mob_spawner/Destroy()
+ . = ..()
+
+/datum/eventkit/mob_spawner/tgui_state(mob/user)
+ return GLOB.tgui_admin_state
+
+/datum/eventkit/mob_spawner/tgui_static_data(mob/user)
+ var/list/data = list()
+
+ data["mob_paths"] = typesof(/mob);
+
+ data["initial_x"] = usr.x;
+ data["initial_y"] = usr.y;
+ data["initial_z"] = usr.z;
+
+ return data
+
+/datum/eventkit/mob_spawner/tgui_data(mob/user)
+ var/list/data = list()
+
+ data["loc_lock"] = loc_lock;
+ if(loc_lock)
+ data["loc_x"] = usr.x;
+ data["loc_y"] = usr.y;
+ data["loc_z"] = usr.z;
+
+ data["path"] = path;
+
+ var/mob/M = new path();
+ if(M)
+ data["default_path_name"] = M.name;
+ data["default_desc"] = M.desc;
+ data["default_flavor_text"] = M.flavor_text;
+ qdel(M);
+
+ return data
+
+/datum/eventkit/mob_spawner/tgui_act(action, list/params)
+ . = ..()
+ if(.)
+ return
+ if(!check_rights_for(usr.client, R_SPAWN))
+ return
+ switch(action)
+ if("select_path")
+ path = params["path"]
+ return TRUE
+ if("loc_lock")
+ loc_lock = !loc_lock
+ return TRUE
+ if("start_spawn")
+ var/confirm = tgui_alert(usr, "Are you sure that you want to start spawning your custom mobs?", "Confirmation", list("Yes", "Cancel"))
+
+ if(confirm != "Yes")
+ return FALSE
+
+ var/amount = params["amount"]
+ var/name = params["name"]
+ var/x = params["x"]
+ var/y = params["y"]
+ var/z = params["z"]
+
+ var/turf/T = locate(x, y, z)
+ if(!T)
+ to_chat(usr, "Those coordinates are outside the boundaries of the map.")
+ return FALSE
+
+ for(var/i = 0, i < amount, i++)
+ if(ispath(path,/turf))
+ var/turf/TU = get_turf(locate(x, y, z))
+ TU.ChangeTurf(path)
+ else
+ var/mob/M = new path(usr.loc)
+
+ M.name = sanitize(name)
+ M.desc = sanitize(params["desc"])
+ M.flavor_text = sanitize(params["flavor_text"])
+
+ /*
+ WIP: Radius around selected coords
+
+ var/list/turf/destTurfs
+ for(var/turf/RT in orange(T, params["r"]))
+ destTurfs += RT
+
+ var/turf/targetTurf = rand(0,length(destTurfs))
+ */
+
+ var/size_mul = params["size_multiplier"]
+ if(isnum(size_mul))
+ M.size_multiplier = size_mul
+ M.update_icon()
+ else
+ to_chat(usr, "Size Multiplier not applied: ([size_mul]) is not a valid input.")
+
+ M.forceMove(T)
+
+ log_and_message_admins("spawned [path] ([name]) at ([x],[y],[z]) [amount] times.")
+
+ return TRUE
+
+/datum/eventkit/mob_spawner/tgui_close(mob/user)
+ . = ..()
+ qdel(src)
+
+/client/verb/eventkit_open_mob_spawner()
+ set category = "EventKit"
+ set name = "Open Mob Spawner"
+ set desc = "Opens an advanced version of the mob spawner."
+
+ if(!check_rights(R_SPAWN))
+ return
+
+ var/datum/eventkit/mob_spawner/spawner = new()
+ spawner.tgui_interact(usr)
diff --git a/code/modules/vore/resizing/resize_vr.dm b/code/modules/vore/resizing/resize_vr.dm
index 4230a82e0b..c168a33416 100644
--- a/code/modules/vore/resizing/resize_vr.dm
+++ b/code/modules/vore/resizing/resize_vr.dm
@@ -74,9 +74,14 @@
/**
* Resizes the mob immediately to the desired mod, animating it growing/shrinking.
* It can be used by anything that calls it.
+ *
+ * Arguments:
+ * * new_size - CHANGE_ME.
+ * * animate - CHANGE_ME. Default: TRUE
+ * * uncapped - CHANGE_ME. Default: FALSE
+ * * ignore_prefs - CHANGE_ME. Default: FALSE
+ * * aura_animation - CHANGE_ME. Default: TRUE
*/
-
-
/mob/living/proc/resize(var/new_size, var/animate = TRUE, var/uncapped = FALSE, var/ignore_prefs = FALSE, var/aura_animation = TRUE)
if(!uncapped)
new_size = clamp(new_size, RESIZE_MINIMUM, RESIZE_MAXIMUM)
diff --git a/tgui/packages/tgui/interfaces/MobSpawner.tsx b/tgui/packages/tgui/interfaces/MobSpawner.tsx
new file mode 100644
index 0000000000..1f98c55570
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/MobSpawner.tsx
@@ -0,0 +1,211 @@
+import { BooleanLike } from '../../common/react';
+import { useBackend, useLocalState } from '../backend';
+import { Button, Dropdown, Flex, Input, Knob, LabeledList, NumberInput, Section, Tabs, TextArea } from '../components';
+import { Window } from '../layouts';
+
+type Data = {
+ path: string;
+
+ default_path_name: string;
+ default_desc: string;
+ default_flavor_text: string;
+
+ default_speak_emotes: string[];
+
+ mob_paths: string[];
+
+ loc_lock: BooleanLike;
+ loc_x: number;
+ loc_y: number;
+ loc_z: number;
+
+ initial_x: number;
+ initial_y: number;
+ initial_z: number;
+};
+
+export const MobSpawner = (props, context) => {
+ const { act, data } = useBackend(context);
+
+ const [tabIndex, setTabIndex] = useLocalState(context, 'panelTabIndex', 0);
+
+ const tabs: any = [];
+
+ tabs[0] = ;
+ tabs[1] = ;
+
+ return (
+
+
+
+ setTabIndex(0)}>
+ General Settings
+
+ setTabIndex(1)}>
+ Vore Settings [WIP]
+
+
+ {tabs[tabIndex] || 'Error'}
+
+
+ );
+};
+
+const GeneralMobSettings = (props, context) => {
+ const { act, data } = useBackend(context);
+
+ const [amount, setAmount] = useLocalState(context, 'amount', 1);
+ const [name, setName] = useLocalState(
+ context,
+ 'name',
+ data.default_path_name
+ );
+ const [desc, setDesc] = useLocalState(context, 'desc', data.default_desc);
+ const [flavorText, setFlavorText] = useLocalState(
+ context,
+ 'flavorText',
+ data.default_flavor_text
+ );
+
+ const [sizeMultiplier, setSizeMultiplier] = useLocalState(
+ context,
+ 'sizeMultiplier',
+ 100
+ );
+
+ const [x, setX] = useLocalState(context, 'x', data.initial_x);
+ const [y, setY] = useLocalState(context, 'y', data.initial_y);
+ const [z, setZ] = useLocalState(context, 'z', data.initial_z);
+
+ const [radius, setRadius] = useLocalState(context, 'radius', 0);
+
+ return (
+ <>
+
+
+
+
+ setX(val)}
+ />
+ setY(val)}
+ />
+ setZ(val)}
+ />
+ act('loc_lock')}
+ />
+
+
+ setRadius(val)}
+ />
+
+
+
+
+
+
+ Description:
+
+
+
+ Flavor Text:
+
+
+
+
+
+ >
+ );
+};
+
+const VoreMobSettings = (props, context) => {
+ const { act, data } = useBackend(context);
+
+ return (
+
+ This Tab is still under construction!
+
+ Functionality will be added in later updates.
+
+ );
+};
diff --git a/vorestation.dme b/vorestation.dme
index e6968594a8..e240917da7 100644
--- a/vorestation.dme
+++ b/vorestation.dme
@@ -2365,6 +2365,8 @@
#include "code\modules\error_handler\error_handler.dm"
#include "code\modules\error_handler\error_viewer.dm"
#include "code\modules\error_handler\~defines.dm"
+#include "code\modules\eventkit\event_machinery.dm"
+#include "code\modules\eventkit\gm_interfaces\mob_spawner.dm"
#include "code\modules\events\apc_damage.dm"
#include "code\modules\events\atmos_leak.dm"
#include "code\modules\events\aurora_caelus.dm"