SchoolDop Modding Guide

Learn how to customize the player, students, routines, hairstyles, reactions, textures, the environment — and write Lua scripts for new mechanics.

1. Player Customization

The player is configured in player.json. This file controls the player character's base appearance and starting stats.

{
  "player": "female",
  "reputation": 0,
  "breastSize": 0.75,
  "hairstyleId": 4
}

Player Fields

2. Students Customization

Students are defined in students.json. Each entry includes identity, appearance, routine, and social attributes. Both male and female students are fully supported. You can freely add new students by creating new entries with unique IDs, or remove existing ones by deleting their entry — the game will load exactly whoever is in the array.

{
  "id": 1,
  "name": "Haruto Katou",
  "gender": "Male",
  "type": "Student",
  "club": "Computer_Science",
  "socialReputation": 35,
  "loveTargetID": -1,
  "faceMaterial": "Male/MaleFace_Red",
  "hairMaterial": "Male/MaleHair_Red",
  "hairstyleID": 0,
  "chestSize": 0,
  "routineID": "1"
}

2.1 Student Fields

2.2 Appearance

Material paths now use gender-based subfolders. Use Male/ for male characters and Female/ for female characters.

⚠ Note: Teachers currently use flat material paths without subfolders (e.g. MaleFace_Standard). This may be unified in a future update.

3. Hairstyle Customization

Hairstyle shapes are defined in hairstyles.json. Each hairstyle has an id and a list of parts, each describing a hair bone's local transform. The hairstyleID field in students.json and player.json references these IDs.

{
  "hairstyles": [
    {
      "id": 0,
      "parts": [
        {
          "objectName": "FrontHair",
          "localPosition": { "x": 0.0, "y": -0.08, "z": 0.13 },
          "localRotation": { "x": 0.0, "y": 0.0, "z": 0.0 },
          "localScale": { "x": 1.0, "y": 1.0, "z": 1.0 },
          "children": []
        }
      ]
    }
  ]
}

3.1 Hairstyle Part Fields

3.2 Built-in Hairstyle IDs

4. Routine Customization

Routines are defined in routines.json. Each entry includes time, destination, pause duration, look direction, and separate animations for movement and arrival.

{
  "hour": 7,
  "minute": 50,
  "isPM": false,
  "destination": "Sit 1 1",
  "pauseSeconds": 0,
  "lookDirectionName": "East",
  "animationOnMove": "none",
  "animationOnArrival": "sit"
}

Routine Fields

Valid Animation Values

5. NPC Reaction Messages

Reaction messages are defined in reactions.json. These are the dialogue lines NPCs speak when they notice suspicious behavior from the player.

{
  "bloodMessage": "Why are you covered in blood?!",
  "weaponMessage": "Why are you carrying a weapon?",
  "bloodAndWeaponMessage": "What the hell happened to you?!",
  "professorMessage": "What are you doing? Stop right now!",
  "corpseMessage": "Oh no... there's a body here!"
}

Reaction Fields

6. NPC Type and Club Limitations

7. Environment Modding

Object placement in the environment is defined in objects.json. This file covers the entire scene — any object in the game world can be repositioned, rotated, or rescaled. Just use the exact in-engine object name and the game will apply your transforms automatically.

{
  "objects": [
    {
      "name": "Name1",
      "position": { "x": 1, "y": 2, "z": 3 },
      "rotation": { "x": 0, "y": 90, "z": 0 },
      "scale": { "x": 2, "y": 2, "z": 2 }
    }
  ]
}

Object Fields

Texture Replacement

Currently, only specific textures can be replaced via StreamingAssets:

Valid Routine Location Points

Destination names in routines must match exactly (capitalization matters):

8. Lua Scripting NEW

⚠️ Lua scripting is experimental in v0.5.x. The API may change or break between updates. Use it for testing and feedback — avoid building large mods until a stable release.

SchoolDop supports Lua mods via the MoonSharp interpreter. Lua scripts can react to game events, spawn and manipulate objects, control NPCs, set up proximity triggers, and run timed logic — all without recompiling the game.

8.1 File Setup

Place all .lua files inside the StreamingAssets/Scripts/ folder. The game loads every file in that folder alphabetically at startup. Custom 3D models (AssetBundles) go in StreamingAssets/Bundles/.

💡 Tip: Files are loaded in alphabetical order, so use prefixes like 00_init.lua, 01_mymod.lua to control load order when multiple scripts depend on each other.

8.2 Event Hooks

Define any of the following global functions in your Lua file and the game will call them automatically:

Hook When it's called Arguments
OnStart() Once, right after all mods are loaded
OnUpdate() Every frame
OnPlayerUpdate(player) Every frame (only when the player exists) player — the player GameObject
OnNPCUpdate(npc) Every frame, once per NPC npc — an NPC GameObject
OnCustomEvent(name, ...) When the game fires a custom C# event name (string) + optional extra args
-- minimal example
function OnStart()
    Game:Print("Mod loaded!")
end

function OnPlayerUpdate(player)
    if Game:IsPlayerRunning() then
        Game:Print("Player is running!")
    end
end
⚠ Performance warning: OnUpdate, OnPlayerUpdate, and OnNPCUpdate run every single frame. Avoid expensive operations (object searches, heavy math) inside them. Prefer timers or proximity triggers for infrequent logic.

8.3 Game API Overview

All functionality is exposed through the global Game object. Below is a full reference grouped by category.

🔧 Constructors

Create Unity value types from Lua.

FunctionReturnsDescription
Game:NewVector3(x, y, z)Vector3Creates a 3D position/direction value
Game:NewColor(r, g, b, a)ColorCreates an RGBA color (values 0–1)
Game:NewRotation(x, y, z)QuaternionCreates a rotation from Euler degrees

📋 Logging

Write to the Unity console. Useful for debugging your mod.

FunctionReturnsDescription
Game:Print(msg)voidLog an info message (prefixed with [Lua])
Game:Warn(msg)voidLog a warning message
Game:Error(msg)voidLog an error message

📦 Spawning Objects

Create primitives, empty containers, or load custom 3D models from AssetBundles.

FunctionReturnsDescription
Game:SpawnCube(pos, scale, color)GameObjectSpawns a colored cube primitive
Game:SpawnSphere(pos, scale, color)GameObjectSpawns a sphere primitive
Game:SpawnCapsule(pos, scale, color)GameObjectSpawns a capsule primitive
Game:SpawnCylinder(pos, scale, color)GameObjectSpawns a cylinder primitive
Game:SpawnPlane(pos, scale, color)GameObjectSpawns a flat plane primitive
Game:CreateEmpty(name, position)GameObjectCreates a named empty object at a position
Game:CreateEmpty(name)GameObjectCreates a named empty object at origin
Game:LoadModel(bundle, asset, pos)GameObjectInstantiates a prefab from an AssetBundle in StreamingAssets/Bundles/
Game:LoadModelRotated(bundle, asset, pos, eulerRot)GameObjectSame as above with an initial rotation

🔍 Finding Objects

Locate GameObjects already present in the scene.

FunctionReturnsDescription
Game:Find(name)GameObjectFinds a scene object by exact name
Game:FindByTag(tag)ListReturns all objects with a given Unity tag
Game:GetPlayer()GameObjectReturns the player's GameObject
Game:GetAllNPCs()ListReturns a list of all NPC GameObjects
Game:GetAllNPCsInRange(center, range)ListReturns NPCs within range units of center

🧭 Transform

Read and write position, rotation, scale, and parent-child hierarchy.

FunctionReturnsDescription
Game:SetPosition(obj, pos)voidTeleports an object to a world position
Game:GetPosition(obj)Vector3Returns the object's world position
Game:SetRotation(obj, euler)voidSets rotation from Euler degrees (Vector3)
Game:SetScale(obj, scale)voidSets the object's local scale
Game:LookAt(obj, target)voidRotates obj to face another GameObject
Game:LookAtPosition(obj, pos)voidRotates obj to face a world position
Game:GetForward(obj)Vector3Returns the object's forward direction vector
Game:SetParent(child, parent)voidParents child under parent (keeps world position)
Game:Unparent(obj)voidDetaches an object from its parent

📐 Distances & Proximity

Measure distances between objects or positions.

FunctionReturnsDescription
Game:Distance(a, b)floatDistance between two GameObjects
Game:DistanceV3(a, b)floatDistance between two Vector3 positions
Game:IsNear(a, b, maxDist)boolTrue if two objects are within maxDist
Game:IsPlayerNear(obj, maxDist)boolTrue if the player is within maxDist of obj
Game:DirectionTo(from, to)Vector3Normalized direction vector from one object to another

⚙️ Components

Add or retrieve Unity components on any GameObject.

FunctionReturnsDescription
Game:AddRigidbody(obj, useGravity)RigidbodyAdds a physics Rigidbody
Game:GetRigidbody(obj)RigidbodyGets the existing Rigidbody
Game:AddBoxCollider(obj, center, size)BoxColliderAdds a box collider
Game:AddSphereCollider(obj, radius)SphereColliderAdds a sphere collider
Game:AddCapsuleCollider(obj, radius, height)CapsuleColliderAdds a capsule collider
Game:AddTrigger(obj, center, size)BoxColliderAdds a box collider set as trigger (isTrigger = true)
Game:AddNavMeshAgent(obj, speed, radius)NavMeshAgentAdds a NavMesh navigation agent
Game:GetNavMeshAgent(obj)NavMeshAgentGets the existing NavMeshAgent
Game:SetNavDestination(obj, dest)voidMoves an agent toward a world position
Game:StopNav(obj)voidStops the agent and clears its path
Game:AddAnimator(obj)AnimatorAdds an Animator component
Game:GetAnimator(obj)AnimatorGets the existing Animator

💡 Lights

Add dynamic lights to any GameObject.

FunctionReturnsDescription
Game:AddLight(obj, type, color, intensity, range)LightAdds a light. type accepts: "point", "spot", "directional", "area"

🎨 Materials & Colors

Modify the visual appearance of objects at runtime.

FunctionReturnsDescription
Game:SetColor(obj, color)voidSets the main albedo color of an object's material
Game:SetMaterial(obj, color, metallic, smoothness)voidReplaces the material with a new Standard shader material
Game:SetEmissive(obj, color)voidEnables emission and sets the glow color

🎬 Animator Parameters

Drive animation state machines by reading and writing Animator parameters.

FunctionReturnsDescription
Game:SetAnimBool(obj, param, value)voidSets a boolean parameter
Game:SetAnimFloat(obj, param, value)voidSets a float parameter
Game:SetAnimInt(obj, param, value)voidSets an integer parameter
Game:SetAnimTrigger(obj, param)voidFires a trigger parameter
Game:GetAnimBool(obj, param)boolReturns the current value of a boolean parameter
Game:GetAnimFloat(obj, param)floatReturns the current value of a float parameter

🧑‍🎓 NPC Control

Interact with the NPC routine and reaction systems.

FunctionReturnsDescription
Game:PauseNPC(obj)voidTemporarily freezes an NPC's routine
Game:ResumeNPC(obj)voidResumes a paused NPC's routine
Game:StopNPC(obj)voidPermanently stops an NPC's routine for the session
Game:IsNPCPanicked(obj)boolReturns true if the NPC is currently panicked
Game:IsNPCReacting(obj)boolReturns true if the NPC is currently reacting to something

🎮 Player State

Read the current state of the player character. These are read-only queries.

FunctionReturnsDescription
Game:IsPlayerRunning()boolTrue if the player is currently running
Game:IsPlayerCrouching()boolTrue if the player is currently crouching
Game:GetPlayerSpeed()floatReturns the player's current movement speed (m/s)
⚠ Limitation: Player state functions are read-only. You cannot set the player's speed, force crouching, or override input from Lua. Direct player control is not exposed in the current API.

🌐 Physics

Apply forces and perform raycasts against the scene geometry.

FunctionReturnsDescription
Game:AddForce(obj, force)voidApplies an impulse force to an object's Rigidbody
Game:Raycast(origin, direction, maxDist)boolReturns true if the ray hits any collider
Game:RaycastGetObject(origin, direction, maxDist)GameObjectReturns the first GameObject hit by the ray, or nil

🗑️ Destroying Objects

FunctionReturnsDescription
Game:Destroy(obj)voidRemoves an object from the scene immediately
Game:DestroyAfter(obj, seconds)voidRemoves an object after a delay

⏱️ Timers

Schedule code to run after a delay or on a repeating interval.

FunctionReturnsDescription
Game:Wait(seconds, callback)voidCalls callback once after seconds
Game:SetInterval(interval, callback)intCalls callback every interval seconds. Returns a timer ID
Game:CancelTimer(id)voidStops a repeating timer by its ID
-- run something after 3 seconds
Game:Wait(3.0, function()
    Game:Print("3 seconds passed!")
end)

-- repeat every 5 seconds, then stop after 30s
local myTimer = Game:SetInterval(5.0, function()
    Game:Print("tick")
end)

Game:Wait(30.0, function()
    Game:CancelTimer(myTimer)
    Game:Print("interval stopped")
end)

📡 Proximity Triggers

Register callbacks that fire automatically when the player or an NPC enters a radius around a target object.

FunctionReturnsDescription
Game:OnPlayerNear(target, radius, callback)voidOne-shot: fires once when the player enters radius around target, then unregisters itself
Game:OnPlayerNearContinuous(target, radius, callback)voidContinuous: fires every frame while the player is within radius
Game:OnPlayerEnterExit(target, radius, onEnter, onExit)voidFires onEnter when the player enters and onExit when they leave. Either argument can be nil
Game:OnNPCNear(target, radius, callback)voidFires once per unique NPC that enters radius around target
function OnStart()
    local marker = Game:SpawnCube(
        Game:NewVector3(5, 0, 10),
        Game:NewVector3(0.5, 0.5, 0.5),
        Game:NewColor(1, 0, 0, 1)
    )

    -- one-shot: fires once when player steps near
    Game:OnPlayerNear(marker, 2.0, function()
        Game:Print("Player reached the marker!")
        Game:Destroy(marker)
    end)

    -- enter / exit zone
    local zone = Game:Find("GardenArea")
    Game:OnPlayerEnterExit(zone, 5.0,
        function() Game:Print("Entered garden") end,
        function() Game:Print("Left garden")   end
    )
end

🛠️ Utility

Miscellaneous helper functions.

FunctionReturnsDescription
Game:Random(min, max)floatRandom float in [min, max]
Game:RandomInt(min, max)intRandom integer in [min, max)
Game:Time()floatSeconds since the game started (Time.time)
Game:DeltaTime()floatTime elapsed since last frame (Time.deltaTime)
Game:RandomPointAround(center, radius)Vector3Random position on the XZ plane within radius of center
Game:IsNull(obj)boolSafe null-check for GameObjects (use instead of obj == nil)
Game:GetName(obj)stringReturns the object's name
Game:SetName(obj, name)voidRenames the object
Game:SetActive(obj, active)voidEnables or disables an object in the scene
Game:IsActive(obj)boolReturns true if the object is active in the hierarchy
⚠ Note on null checks: Always use Game:IsNull(obj) to test if a GameObject is valid. In MoonSharp, a destroyed C# object is not nil in Lua — it is a live reference to a dead C# object, so obj == nil will return false even after Game:Destroy(obj).

8.4 Current Limitations

The following actions are not possible with the current Lua API:

8.5 Full Example Mod

-- example_mod.lua
-- Spawns a glowing orb. When the player gets close it explodes
-- into smaller spheres and logs a message every 10 seconds.

local orb
local tickTimer

function OnStart()
    -- spawn the orb above the garden
    local pos = Game:NewVector3(0, 1.5, 8)
    local scale = Game:NewVector3(0.6, 0.6, 0.6)
    local color = Game:NewColor(0.2, 0.8, 1, 1)

    orb = Game:SpawnCube(pos, scale, color)
    Game:SetEmissive(orb, Game:NewColor(0, 0.5, 1, 1))
    Game:SetName(orb, "GlowOrb")

    -- start a repeating log
    tickTimer = Game:SetInterval(10.0, function()
        Game:Print("Orb is still alive at t=" .. Game:Time())
    end)

    -- explode when the player steps within 2 units
    Game:OnPlayerNear(orb, 2.0, function()
        Explode()
    end)
end

function Explode()
    if Game:IsNull(orb) then return end

    local center = Game:GetPosition(orb)
    Game:Destroy(orb)
    Game:CancelTimer(tickTimer)

    -- spawn 6 small debris spheres
    for i = 1, 6 do
        local offset = Game:RandomPointAround(center, 1.5)
        local debris = Game:SpawnSphere(
            offset,
            Game:NewVector3(0.2, 0.2, 0.2),
            Game:NewColor(Game:Random(0,1), Game:Random(0,1), Game:Random(0,1), 1)
        )
        Game:DestroyAfter(debris, 3.0)
    end

    Game:Print("Orb exploded!")
end

9. Tips & Best Practices

⚠ Note: Future updates will allow modding of all buildings, decorations, and easter eggs. Lua scripting support will be expanded with audio playback, UI overlays, persistent save data, and more game-state write access.