Spaces:
Running
Running
math.randomseed(math.floor(Time.UnixMilli() % 100000)) | |
Modules = { | |
--gigax = "github.com/GigaxGames/integrations/cubzh:5025b99", | |
pathfinding = "github.com/caillef/cubzh-library/pathfinding:f8c4315", | |
floating_island_generator = "github.com/caillef/cubzh-library/floating_island_generator:82d22a5", | |
} | |
Config = { | |
Items = { "pratamacam.squirrel" }, | |
} | |
math.randomseed(math.floor(Time.UnixMilli() % 100000)) | |
local easy_onboarding = {} | |
local currentStep = 0 | |
local steps = {} | |
local stopCallbackData | |
easy_onboarding.startOnboarding = function(self, config) | |
currentStep = 1 | |
steps = config.steps | |
stopCallbackData = steps[1].start(self, currentStep) | |
end | |
easy_onboarding.next = function(self) | |
if not steps[currentStep] then | |
return | |
end | |
steps[currentStep].stop(self, stopCallbackData) | |
currentStep = currentStep + 1 | |
if not steps[currentStep] then | |
return | |
end | |
stopCallbackData = steps[currentStep].start(self, currentStep) | |
end | |
local onboardingConfig = { | |
steps = { | |
{ | |
start = function(onboarding, step) | |
local ui = require("uikit") | |
local data = {} | |
data.ui = ui:createFrame(Color(0, 0, 0, 0.5)) | |
local text = ui:createText("1/3 - Hold click and drag to move the camera.", Color.White) | |
text:setParent(data.ui) | |
data.ui.parentDidResize = function() | |
data.ui.Width = text.Width + text.Height | |
data.ui.Height = text.Height * 2 | |
text.pos = { data.ui.Width * 0.5 - text.Width * 0.5, data.ui.Height * 0.5 - text.Height * 0.5 } | |
data.ui.pos = | |
{ Screen.Width * 0.5 - data.ui.Width * 0.5, Screen.Height * 0.2 - data.ui.Height * 0.5 } | |
end | |
data.ui:parentDidResize() | |
data.listener = LocalEvent:Listen(LocalEvent.Name.PointerDrag, function() | |
Timer(1, function() | |
print("next") | |
onboarding:next() | |
end) | |
data.listener:Remove() | |
end) | |
return data | |
end, | |
stop = function(_, data) | |
data.ui:remove() | |
end, | |
}, | |
{ | |
start = function(onboarding, step) | |
local ui = require("uikit") | |
local data = {} | |
data.ui = ui:createFrame(Color(0, 0, 0, 0.5)) | |
local text = ui:createText("2/3 - Use WASD/ZQSD to move", Color.White) | |
text:setParent(data.ui) | |
data.ui.parentDidResize = function() | |
data.ui.Width = text.Width + text.Height | |
data.ui.Height = text.Height * 2 | |
text.pos = { data.ui.Width * 0.5 - text.Width * 0.5, data.ui.Height * 0.5 - text.Height * 0.5 } | |
data.ui.pos = | |
{ Screen.Width * 0.5 - data.ui.Width * 0.5, Screen.Height * 0.2 - data.ui.Height * 0.5 } | |
end | |
data.ui:parentDidResize() | |
data.listener = LocalEvent:Listen(LocalEvent.Name.KeyboardInput, function() | |
Timer(1, function() | |
onboarding:next() | |
end) | |
data.listener:Remove() | |
end) | |
return data | |
end, | |
stop = function(_, data) | |
data.ui:remove() | |
end, | |
}, | |
{ | |
start = function(onboarding, step) | |
local ui = require("uikit") | |
local data = {} | |
data.ui = ui:createFrame(Color(0, 0, 0, 0.5)) | |
local text = ui:createText("3/3 - Press Enter in front of a NPC to chat", Color.White) | |
text:setParent(data.ui) | |
data.ui.parentDidResize = function() | |
data.ui.Width = text.Width + text.Height | |
data.ui.Height = text.Height * 2 | |
text.pos = { data.ui.Width * 0.5 - text.Width * 0.5, data.ui.Height * 0.5 - text.Height * 0.5 } | |
data.ui.pos = | |
{ Screen.Width * 0.5 - data.ui.Width * 0.5, Screen.Height * 0.2 - data.ui.Height * 0.5 } | |
end | |
data.ui:parentDidResize() | |
data.listener = LocalEvent:Listen(LocalEvent.Name.KeyboardInput, function(_, keycode, _, down) | |
print(keycode) | |
if keycode == require("inputcodes").RETURN then | |
print("triggered") | |
Timer(1, function() | |
onboarding:next() | |
end) | |
data.listener:Remove() | |
end | |
end) | |
return data | |
end, | |
stop = function(_, data) | |
data.ui:remove() | |
end, | |
}, | |
}, | |
} | |
Config = { | |
Items = { "pratamacam.squirrel" }, | |
} | |
local gigax = {} | |
local CUBZH_API_TOKEN = | |
"H4gjL-e9kvLF??2pz6oh=kJL497cBnsyCrQFdVkFadUkLnIaEamroYHb91GywMXrbGeDdmTiHxi8EqmJduCKPrDnfqWsjGuF0JJCUTrasGcBfGx=tlJCjq5q8jhVHWL?krIE74GT9AJ7qqX8nZQgsDa!Unk8GWaqWcVYT-19C!tCo11DcLvrnJPEOPlSbH7dDcXmAMfMEf1ZwZ1v1C9?2/BjPDeiAVTRlLFilwRFmKz7k4H-kCQnDH-RrBk!ZHl7" | |
local API_URL = "https://gig.ax" | |
local TRIGGER_AREA_SIZE = Number3(60, 30, 60) | |
local headers = { | |
["Content-Type"] = "application/json", | |
["Authorization"] = CUBZH_API_TOKEN, | |
} | |
-- HELPERS | |
local _helpers = {} | |
_helpers.lookAt = function(obj, target) | |
if not target then | |
require("ease"):linear(obj, 0.1).Forward = obj.initialForward | |
obj.Tick = nil | |
return | |
end | |
obj.Tick = function(self, _) | |
_helpers.lookAtHorizontal(self, target) | |
end | |
end | |
_helpers.lookAtHorizontal = function(o1, o2) | |
local n3_1 = Number3.Zero | |
local n3_2 = Number3.Zero | |
n3_1:Set(o1.Position.X, 0, o1.Position.Z) | |
n3_2:Set(o2.Position.X, 0, o2.Position.Z) | |
require("ease"):linear(o1, 0.1).Forward = n3_2 - n3_1 | |
end | |
-- Function to calculate distance between two positions | |
_helpers.calculateDistance = function(_, pos1, pos2) | |
local dx = pos1.X - pos2.X | |
local dy = pos1.Y - pos2.Y | |
local dz = pos1.Z - pos2.Z | |
return math.sqrt(dx * dx + dy * dy + dz * dz) | |
end | |
_helpers.findClosestLocation = function(_, position, locationData) | |
if not locationData then | |
return | |
end | |
local closestLocation = nil | |
local smallestDistance = math.huge -- Large initial value | |
for _, location in pairs(locationData) do | |
local distance = _helpers:calculateDistance( | |
position, | |
Map:WorldToBlock(Number3(location.position.x, location.position.y, location.position.z)) | |
) | |
if distance < smallestDistance then | |
smallestDistance = distance | |
closestLocation = location | |
end | |
end | |
-- Closest location found, now send its ID to update the character's location | |
return closestLocation | |
end | |
if IsClient then | |
local simulation = {} | |
local npcDataClientById = {} | |
local waitingLinkNPCs = {} | |
local skillCallbacks = {} | |
local gigaxHttpClient = {} | |
gigaxHttpClient.registerMainCharacter = function(_, engineId, locationId, callback) | |
local body = JSON:Encode({ | |
name = Player.Username, | |
physical_description = "A human playing the game", | |
current_location_id = locationId, | |
position = { x = 0, y = 0, z = 0 }, | |
}) | |
local apiUrl = API_URL .. "/api/character/company/main?engine_id=" .. engineId | |
HTTP:Post(apiUrl, headers, body, function(response) | |
if response.StatusCode ~= 200 then | |
print("Error creating or fetching main character: " .. response.StatusCode) | |
return | |
end | |
callback(response.Body) | |
end) | |
end | |
gigaxHttpClient.stepMainCharacter = function(_, engineId, characterId, skill, content, npcName, npcId, callback) | |
if not engineId then | |
return | |
end | |
local stepUrl = API_URL .. "/api/character/" .. characterId .. "/step-no-ws?engine_id=" .. engineId | |
local body = JSON:Encode({ | |
character_id = characterId, | |
skill = skill, | |
target_name = npcName, | |
target = npcId, | |
content = content, | |
}) | |
HTTP:Post(stepUrl, headers, body, function(response) | |
if response.StatusCode ~= 200 then | |
print("Error stepping character: " .. response.StatusCode) | |
return | |
end | |
callback(response.Body) | |
end) | |
end | |
gigaxHttpClient.updateCharacterPosition = function(_, engineId, characterId, locationId, position, callback) | |
local body = JSON:Encode({ | |
current_location_id = locationId, | |
position = { x = position.X, y = position.Y, z = position.Z }, | |
}) | |
local apiUrl = API_URL .. "/api/character/" .. characterId .. "?engine_id=" .. engineId | |
HTTP:Post(apiUrl, headers, body, function(response) | |
if response.StatusCode ~= 200 then | |
print("Error updating character location: " .. response.StatusCode) | |
return | |
end | |
if callback then | |
callback(response.Body) | |
end | |
end) | |
end | |
local onEndData | |
local prevAction | |
local function npcResponse(actionData) | |
local currentAction = string.lower(actionData.skill.name) | |
if onEndData and skillCallbacks[prevAction].onEndCallback then | |
skillCallbacks[prevAction].onEndCallback(gigax, onEndData, currentAction) | |
end | |
local callback = skillCallbacks[currentAction].callback | |
prevAction = string.lower(actionData.skill.name) | |
if not callback then | |
return | |
end | |
onEndData = callback(gigax, actionData, simulation.config) | |
end | |
local function registerEngine(config) | |
local apiUrl = API_URL .. "/api/engine/company/" | |
simulation.locations = {} | |
simulation.NPCs = {} | |
simulation.config = config | |
simulation.player = Player | |
-- Prepare the data structure expected by the backend | |
local engineData = { | |
name = Player.UserID .. ":" .. config.simulationName, | |
description = config.simulationDescription, | |
NPCs = {}, | |
locations = {}, | |
radius, | |
} | |
for _, npc in pairs(config.NPCs) do | |
simulation.NPCs[npc.name] = { | |
name = npc.name, | |
physical_description = npc.physicalDescription, | |
psychological_profile = npc.psychologicalProfile, | |
initial_reflections = npc.initialReflections, | |
current_location_name = npc.currentLocationName, | |
skills = config.skills, | |
} | |
table.insert(engineData.NPCs, simulation.NPCs[npc.name]) | |
end | |
for _, loc in ipairs(config.locations) do | |
simulation.locations[loc.name] = { | |
name = loc.name, | |
position = { x = loc.position.X, y = loc.position.Y, z = loc.position.Z }, | |
description = loc.description, | |
} | |
table.insert(engineData.locations, simulation.locations[loc.name]) | |
end | |
local body = JSON:Encode(engineData) | |
HTTP:Post(apiUrl, headers, body, function(res) | |
if res.StatusCode ~= 201 then | |
print("Error updating engine: " .. res.StatusCode) | |
return | |
end | |
-- Decode the response body to extract engine and location IDs | |
local responseData = JSON:Decode(res.Body) | |
-- Save the engine_id for future use | |
simulation.engineId = responseData.engine.id | |
-- Saving all the _ids inside locationData table: | |
for _, loc in ipairs(responseData.locations) do | |
simulation.locations[loc.name]._id = loc._id | |
end | |
-- same for characters: | |
for _, npc in pairs(responseData.NPCs) do | |
simulation.NPCs[npc.name]._id = npc._id | |
simulation.NPCs[npc.name].position = Number3(npc.position.x, npc.position.y, npc.position.z) | |
end | |
gigaxHttpClient:registerMainCharacter( | |
simulation.engineId, | |
simulation.locations[config.startingLocationName]._id, | |
function(body) | |
simulation.character = JSON:Decode(body) | |
for name, npc in pairs(waitingLinkNPCs) do | |
npc._id = simulation.NPCs[name]._id | |
npc.name = name | |
npcDataClientById[npc._id] = npc | |
end | |
Timer(1, true, function() | |
local position = Map:WorldToBlock(Player.Position) | |
gigax:updateCharacterPosition(simulation, simulation.character._id, position) | |
end) | |
end | |
) | |
end) | |
end | |
findTargetNpc = function(player) | |
if not simulation or type(simulation.NPCs) ~= "table" then | |
return | |
end | |
local closerDist = 1000 | |
local closerNpc | |
for _, npc in pairs(simulation.NPCs) do | |
local dist = (npc.position - player.Position).Length | |
if closerDist > dist then | |
closerDist = dist | |
closerNpc = npc | |
end | |
end | |
if closerDist > 50 then | |
return | |
end -- max distance is 50 | |
return closerNpc | |
end | |
gigax.action = function(_, data) | |
local npc = findTargetNpc(Player) | |
if not npc then | |
return | |
end | |
local content = data.content | |
data.content = nil | |
gigaxHttpClient:stepMainCharacter( | |
simulation.engineId, | |
simulation.character._id, | |
data, | |
content, | |
npc.name, | |
npc._id, | |
function(body) | |
local actions = JSON:Decode(body) | |
for _, action in ipairs(actions) do | |
npcResponse(action) | |
end | |
end | |
) | |
end | |
gigax.getNpc = function(_, id) | |
return npcDataClientById[id] | |
end | |
local skillOnAction = function(actionType, callback, onEndCallback) | |
skillCallbacks[actionType] = { | |
callback = callback, | |
onEndCallback = onEndCallback, | |
} | |
end | |
local prevSyncPosition | |
gigax.updateCharacterPosition = function(_, simulation, characterId, position) | |
if not simulation then | |
return | |
end | |
if position == prevSyncPosition then | |
return | |
end | |
prevSyncPosition = position | |
local closest = _helpers:findClosestLocation(position, simulation.locations) | |
if not closest then | |
print("can't update character position: no closest location found, id:", characterId, position) | |
return | |
end | |
if not characterId then | |
return | |
end | |
gigaxHttpClient:updateCharacterPosition(simulation.engineId, characterId, closest._id, position) | |
end | |
local function createNPC(name, currentPosition, rotation) | |
-- Create the NPC's Object and Avatar | |
local NPC = {} | |
NPC.object = Object() | |
World:AddChild(NPC.object) | |
NPC.object.Position = currentPosition or Number3(0, 0, 0) | |
NPC.object.Scale = 0.5 | |
NPC.object.Physics = PhysicsMode.Trigger | |
NPC.object.CollisionBox = Box({ | |
-TRIGGER_AREA_SIZE.Width * 0.5, | |
math.min(-TRIGGER_AREA_SIZE.Height, NPC.object.CollisionBox.Min.Y), | |
-TRIGGER_AREA_SIZE.Depth * 0.5, | |
}, { | |
TRIGGER_AREA_SIZE.Width * 0.5, | |
math.max(TRIGGER_AREA_SIZE.Height, NPC.object.CollisionBox.Max.Y), | |
TRIGGER_AREA_SIZE.Depth * 0.5, | |
}) | |
local text = Text() | |
text.Text = " " .. string.upper(string.sub(name, 4, 4)) .. string.sub(name, 5, #name) .. " " | |
text:SetParent(NPC.object) | |
text.Type = TextType.Screen | |
text.IsUnlit = true | |
text.LocalPosition.Y = 36 | |
text.FontSize = 22 | |
text.Font = Font.Noto | |
NPC.object.OnCollisionBegin = function(self, other) | |
if other ~= Player then | |
return | |
end | |
_helpers.lookAt(self.avatarContainer, other) | |
end | |
NPC.object.OnCollisionEnd = function(self, other) | |
if other ~= Player then | |
return | |
end | |
_helpers.lookAt(self.avatarContainer, nil) | |
end | |
local container = Object() | |
container.Rotation:Set(rotation or Number3.Zero) | |
container.initialForward = container.Forward:Copy() | |
container:SetParent(NPC.object) | |
container.Physics = PhysicsMode.Trigger | |
NPC.object.avatarContainer = container | |
NPC.avatar = require("avatar"):get(name) | |
NPC.avatar:SetParent(NPC.object.avatarContainer) | |
NPC.name = name | |
NPC.object.onIdle = function() | |
local animations = NPC.avatar.Animations | |
NPC.object.avatarContainer.LocalRotation = { 0, 0, 0 } | |
if not animations or animations.Idle.IsPlaying then | |
return | |
end | |
if animations.Walk.IsPlaying then | |
animations.Walk:Stop() | |
end | |
animations.Idle:Play() | |
end | |
NPC.object.onMove = function() | |
local animations = NPC.avatar.Animations | |
NPC.object.avatarContainer.LocalRotation = { 0, 0, 0 } | |
if not animations or animations.Walk.IsPlaying then | |
return | |
end | |
if animations.Idle.IsPlaying then | |
animations.Idle:Stop() | |
end | |
animations.Walk:Play() | |
end | |
waitingLinkNPCs[name] = NPC | |
-- review this to update location and position | |
Timer(1, true, function() | |
if not simulation then | |
return | |
end | |
local position = Map:WorldToBlock(NPC.object.Position) | |
local prevPosition = NPC.object.prevSyncPosition | |
if prevPosition == position then | |
return | |
end | |
gigax:updateCharacterPosition(simulation, NPC._id, position) | |
NPC.object.prevSyncPosition = position | |
end) | |
return NPC | |
end | |
gigax.setConfig = function(_, config) | |
for _, elem in ipairs(config.skills) do | |
skillOnAction(string.lower(elem.name), elem.callback, elem.onEndCallback) | |
elem.callback = nil | |
elem.onEndCallback = nil | |
end | |
for _, elem in ipairs(config.NPCs) do | |
createNPC(elem.name, elem.position, elem.rotation) | |
end | |
registerEngine(config) | |
end | |
end | |
-- Function to spawn a squirrel above the player | |
function spawnSquirrelAbovePlayer(player) | |
local squirrel = Shape(Items.pratamacam.squirrel) | |
squirrel:SetParent(World) | |
squirrel.Position = player.Position + Number3(0, 20, 0) | |
-- make scale smaller | |
squirrel.LocalScale = 0.5 | |
-- remove collision | |
squirrel.Physics = PhysicsMode.Dynamic | |
-- rotate it 90 degrees to the right | |
squirrel.Rotation = { 0, math.pi * 0.5, 0 } | |
-- this would make squirrel.Rotation = player.Rotation | |
World:AddChild(squirrel) | |
return squirrel | |
end | |
local SIMULATION_NAME = "Islands" .. tostring(math.random()) | |
local SIMULATION_DESCRIPTION = "Three floating islands." | |
local occupiedPositions = {} | |
local skills = { | |
{ | |
name = "SAY", | |
description = "Say smthg out loud", | |
parameter_types = { "character", "content" }, | |
callback = function(client, action) | |
local npc = client:getNpc(action.character_id) | |
if not npc then | |
print("Can't find npc") | |
return | |
end | |
dialog:create(action.content, npc.avatar.Head) | |
print(string.format("%s: %s", npc.name, action.content)) | |
end, | |
action_format_str = "{protagonist_name} said '{content}' to {target_name}", | |
}, | |
{ | |
name = "MOVE", | |
description = "Move to a new location", | |
parameter_types = { "location" }, | |
callback = function(client, action, config) | |
local targetName = action.target_name | |
local targetPosition = findLocationByName(targetName, config) | |
if not targetPosition then | |
print("tried to move to an unknown place", targetName) | |
return | |
end | |
local npc = client:getNpc(action.character_id) | |
dialog:create("I'm going to " .. targetName, npc.avatar.Head) | |
print(string.format("%s: %s", npc.name, "I'm going to " .. targetName)) | |
local origin = Map:WorldToBlock(npc.object.Position) | |
local destination = Map:WorldToBlock(targetPosition) + Number3(math.random(-1, 1), 0, math.random(-1, 1)) | |
local canMove = pathfinding:moveObjectTo(npc.object, origin, destination) | |
if not canMove then | |
dialog:create("I can't go there", npc.avatar.Head) | |
return | |
end | |
end, | |
action_format_str = "{protagonist_name} moved to {target_name}", | |
}, | |
{ | |
name = "GREET", | |
description = "Greet a character by waving your hand at them", | |
parameter_types = { "character" }, | |
callback = function(client, action) | |
local npc = client:getNpc(action.character_id) | |
if not npc then | |
print("Can't find npc") | |
return | |
end | |
dialog:create("<Greets you warmly!>", npc.avatar.Head) | |
print(string.format("%s: %s", npc.name, "<Greets you warmly!>")) | |
npc.avatar.Animations.SwingRight:Play() | |
end, | |
action_format_str = "{protagonist_name} waved their hand at {target_name} to greet them", | |
}, | |
{ | |
name = "JUMP", | |
description = "Jump in the air", | |
parameter_types = {}, | |
callback = function(client, action) | |
local npc = client:getNpc(action.character_id) | |
if not npc then | |
print("Can't find npc") | |
return | |
end | |
dialog:create("<Jumps in the air!>", npc.avatar.Head) | |
print(string.format("%s: %s", npc.name, "<Jumps in the air!>")) | |
npc.object.avatarContainer.Physics = PhysicsMode.Dynamic | |
npc.object.avatarContainer.Velocity.Y = 50 | |
Timer(3, function() | |
npc.object.avatarContainer.Physics = PhysicsMode.Trigger | |
end) | |
end, | |
action_format_str = "{protagonist_name} jumped up in the air for a moment.", | |
}, | |
{ | |
name = "FOLLOW", | |
description = "Follow a character around for a while", | |
parameter_types = { "character" }, | |
callback = function(client, action) | |
local npc = client:getNpc(action.character_id) | |
if not npc then | |
print("Can't find npc") | |
return | |
end | |
dialog:create("I'm following you", npc.avatar.Head) | |
print(string.format("%s: %s", npc.name, "I'm following you")) | |
followHandler = pathfinding:followObject(npc.object, Player) | |
return { | |
followHandler = followHandler, | |
} | |
end, | |
onEndCallback = function(_, data) | |
data.followHandler:Stop() | |
end, | |
action_format_str = "{protagonist_name} followed {target_name} for a while.", | |
}, | |
{ | |
name = "EXPLODE", | |
description = "Explodes in a fireball - Hell yeah!", | |
parameter_types = { "character" }, | |
callback = function(client, action) | |
local npc = client:getNpc(action.character_id) | |
if not npc then | |
print("Can't find npc") | |
return | |
end | |
require("explode"):shapes(npc.avatar) | |
dialog:create("*boom*", npc.avatar.Head) | |
--print(string.format("%s: %s", npc.name, "EXPLODING")) | |
npc.avatar.IsHidden = true | |
Timer(5, function() | |
dialog:create("Aaaaand... I'm back!", npc.avatar.Head) | |
npc.avatar.IsHidden = false | |
end) | |
end, | |
action_format_str = "{protagonist_name} exploded!", | |
},--[[ | |
{ | |
name = "GIVEAPPLE", | |
description = "Give a pice of bread (or a baguette) to someone", | |
parameter_types = {"character"}, | |
callback = function(client, action) | |
local npc = client:getNpc(action.character_id) | |
if not npc then print("Can't find npc") return end | |
local shape = MutableShape() | |
shape:AddBlock(Color.Red, 0, 0, 0) | |
shape.Scale = 4 | |
Player:EquipRightHand(shape) | |
dialog:create("Here is an apple for you!", npc.avatar.Head) | |
end, | |
action_format_str = "{protagonist_name} gave you a piece of bread!" | |
}, | |
{ | |
name = "SCALEUP", | |
description = "Double your height", | |
parameter_types = {"character"}, | |
callback = function(client, action) | |
local npc = client:getNpc(action.character_id) | |
if not npc then print("Can't find npc") return end | |
npc.object.Scale = npc.object.Scale * 2 | |
dialog:create("I am taller than you now!", npc.avatar.Head) | |
end, | |
action_format_str = "{protagonist_name} doubled his height!" | |
},--]] | |
{ | |
name = "GIVEHAT", | |
description = "Give a party hat to someone", | |
parameter_types = { "character" }, | |
callback = function(client, action) | |
local npc = client:getNpc(action.character_id) | |
if not npc then | |
print("Can't find npc") | |
return | |
end | |
Object:Load("claire.party_hat", function(obj) | |
require("hierarchyactions"):applyToDescendants(obj, { includeRoot = true }, function(o) | |
o.Physics = PhysicsMode.Disabled | |
end) | |
Player:EquipHat(obj) | |
end) | |
dialog:create("Let's get the party started!", npc.avatar.Head) | |
end, | |
action_format_str = "{protagonist_name} gave you a piece of bread!", | |
}, | |
{ | |
name = "FLYINGSQUIRREL", | |
description = "Summon a flying squirrel - only the scientist can do this!!", | |
parameter_types = {}, | |
callback = function(client, action) | |
local npc = client:getNpc(action.character_id) | |
if not npc then | |
print("Can't find npc") | |
return | |
end | |
local squirrel = spawnSquirrelAbovePlayer(Player) | |
dialog:create("Wooh, squirrel!", npc.avatar.Head) | |
-- make it disappear after a while | |
Timer(5, function() | |
squirrel:RemoveFromParent() | |
squirrel = nil | |
end) | |
end, | |
action_format_str = "{protagonist_name} summoned a flying squirrel! It's vibrating with excitement!", | |
}, | |
} | |
local locations = { | |
{ | |
name = "Scientist Island", | |
description = "A small island with a scientist and its pet chilling.", | |
}, | |
{ | |
name = "Baker Island", | |
description = "A small bakery on a floating island in the sky.", | |
}, | |
{ | |
name = "Pirate Island", | |
description = "A small floating island in the sky with a pirate and its ship.", | |
}, | |
{ | |
name = "Center", | |
description = "Center point between the three islands.", | |
}, | |
} | |
local NPCs = { | |
{ | |
name = "npcscientist", | |
physicalDescription = "Short, with a stern expression and sharp eyes", | |
psychologicalProfile = "Grumpy but insightful, suspicious yet intelligent", | |
currentLocationName = "Scientist Island", | |
initialReflections = { | |
"I just arrived on this island to feed my pet, he loves tulips so much.", | |
"I was just eating before I stood up to start the radio, I don't know which song I should start", | |
"I am a scientist that works on new pets for everyone, so that each individual can have the pet of their dreams", | |
"I am a bit allergic to the tulip but Fredo my pet loves it so much, I have to dock here with my vehicle. The pet is placed at the back of my flying scooter when we move to another place.", | |
}, | |
}, | |
{ | |
name = "npcbaker", | |
physicalDescription = "Tall, with a solemn demeanor and thoughtful eyes", | |
psychologicalProfile = "Wise and mysterious, calm under pressure", | |
currentLocationName = "Baker Island", | |
initialReflections = { | |
"I am a baker and I make food for everyone that pass by.", | |
"I am a bit stressed that the flour didn't arrived yet, my cousin Joe should arrive soon with the delivery but he is late and I worry a bit.", | |
"I love living here on these floating islands, the view is amazing from my wind mill.", | |
"I like to talk to strangers like the pirate that just arrived or the scientist coming time to time to feed his pet.", | |
}, | |
}, | |
{ | |
name = "npcpirate", | |
physicalDescription = "Average height, with bright green eyes and a warm smile", | |
psychologicalProfile = "Friendly and helpful, quick-witted and resourceful", | |
currentLocationName = "Pirate Island", | |
initialReflections = { | |
"Ahoy, matey! I'm Captain Ruby Storm, a fearless lass from the seven skies.", | |
"I've docked me floating ship on this here floating isle to sell me wares (almost legally) retrieved treasures from me last daring adventure.", | |
"So, who be lookin' to trade with a swashbuckler like meself?", | |
}, | |
}, | |
} | |
local gigaxWorldConfig = { | |
simulationName = SIMULATION_NAME, | |
simulationDescription = SIMULATION_DESCRIPTION, | |
startingLocationName = "Center", | |
skills = skills, | |
locations = locations, | |
NPCs = NPCs, | |
} | |
findLocationByName = function(targetName, config) | |
for _, node in ipairs(config.locations) do | |
if string.lower(node.name) == string.lower(targetName) then | |
return node.position | |
end | |
end | |
end | |
Client.OnWorldObjectLoad = function(obj) | |
if obj.Name == "pirate_ship" then | |
obj.Scale = 1 | |
end | |
if obj.Name == "NPC_scientist" then | |
local pos = obj.Position:Copy() | |
gigaxWorldConfig.locations[1].position = pos | |
gigaxWorldConfig.NPCs[1].position = pos | |
gigaxWorldConfig.NPCs[1].rotation = obj.Rotation:Copy() | |
obj:RemoveFromParent() | |
elseif obj.Name == "NPC_baker" then | |
local pos = obj.Position:Copy() | |
gigaxWorldConfig.locations[2].position = pos | |
gigaxWorldConfig.NPCs[2].position = pos | |
gigaxWorldConfig.NPCs[2].rotation = obj.Rotation:Copy() | |
obj:RemoveFromParent() | |
elseif obj.Name == "NPC_pirate" then | |
local pos = obj.Position:Copy() | |
gigaxWorldConfig.locations[3].position = pos | |
gigaxWorldConfig.NPCs[3].position = pos | |
gigaxWorldConfig.NPCs[3].rotation = obj.Rotation:Copy() | |
obj:RemoveFromParent() | |
end | |
end | |
Client.OnStart = function() | |
easy_onboarding:startOnboarding(onboardingConfig) | |
require("object_skills").addStepClimbing(Player, { | |
mapScale = MAP_SCALE, | |
collisionGroups = Map.CollisionGroups, | |
}) | |
gigaxWorldConfig.locations[4].position = Number3(Map.Width * 0.5, Map.Height - 2, Map.Depth * 0.5) * Map.Scale | |
floating_island_generator:generateIslands({ | |
nbIslands = 20, | |
minSize = 4, | |
maxSize = 7, | |
safearea = 200, -- min dist of islands from 0,0,0 | |
dist = 750, -- max dist of islands | |
}) | |
local ambience = require("ambience") | |
ambience:set(ambience.dusk) | |
sfx = require("sfx") | |
Player.Head:AddChild(AudioListener) | |
dropPlayer = function() | |
Player.Position = Number3(Map.Width * 0.5, Map.Height + 10, Map.Depth * 0.5) * Map.Scale | |
Player.Rotation = { 0, 0, 0 } | |
Player.Velocity = { 0, 0, 0 } | |
end | |
World:AddChild(Player) | |
dropPlayer() | |
dialog = require("dialog") | |
dialog:setMaxWidth(400) | |
pathfinding:createPathfindingMap() | |
gigax:setConfig(gigaxWorldConfig) | |
print(math.random(20)) | |
local randomNames = { "aduermael", "soliton", "gdevillele", "caillef", "voxels", "petroglyph" } | |
Player.Avatar:load({ usernameOrId = randomNames[math.random(#randomNames)] }) | |
end | |
Client.Action1 = function() | |
if Player.IsOnGround then | |
sfx("hurtscream_1", { Position = Player.Position, Volume = 0.4 }) | |
Player.Velocity.Y = 100 | |
if Player.Motion.X == 0 and Player.Motion.Z == 0 then | |
-- only play jump action when jumping without moving to avoid wandering around to trigger NPCs | |
gigax:action({ | |
name = "JUMP", | |
description = "Jump in the air", | |
parameter_types = {}, | |
action_format_str = "{protagonist_name} jumped up in the air for a moment.", | |
}) | |
end | |
end | |
end | |
Client.Tick = function(dt) | |
if Player.Position.Y < -500 then | |
dropPlayer() | |
end | |
end | |
Client.OnChat = function(payload) | |
local msg = payload.message | |
Player:TextBubble(msg, 3, true) | |
sfx("waterdrop_2", { Position = Player.Position, Pitch = 1.1 + math.random() * 0.5 }) | |
gigax:action({ | |
name = "SAY", | |
description = "Say smthg out loud", | |
parameter_types = { "character", "content" }, | |
action_format_str = "{protagonist_name} said '{content}' to {target_name}", | |
content = msg, | |
}) | |
print(payload.message) | |
return true | |
end | |