|
Modules = { |
|
|
|
pathfinding = "github.com/caillef/cubzh-library/pathfinding:f8c4315", |
|
floating_island_generator = "github.com/caillef/cubzh-library/floating_island_generator:82d22a5", |
|
} |
|
|
|
Config = { |
|
Items = { "pratamacam.squirrel" }, |
|
} |
|
|
|
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(currentStep, self) |
|
end |
|
|
|
easy_onboarding.next = function(self) |
|
steps[currentStep].stop(self, stopCallbackData) |
|
currentStep = currentStep + 1 |
|
stopCallbackData = steps[currentStep].start(self, currentStep) |
|
end |
|
|
|
local onboarding_config = { |
|
steps = { |
|
{ |
|
start = function(onboarding, step) |
|
local ui = require("uikit") |
|
local data = {} |
|
data.ui = ui:createText("Hold click and drag to move camera") |
|
data.ui.pos = { Screen.Width * 0.5 - data.ui.Width * 0.5, Screen.Height * 0.5 - data.ui.Height * 0.5 } |
|
return data |
|
end, |
|
stop = function(onboarding, data) |
|
data:remove() |
|
end, |
|
}, |
|
{ |
|
start = function(onboarding, step) |
|
local ui = require("uikit") |
|
local data = {} |
|
data.ui = ui:createText("Use WASD/ZQSD to move") |
|
data.ui.pos = { Screen.Width * 0.5 - data.ui.Width * 0.5, Screen.Height * 0.5 - data.ui.Height * 0.5 } |
|
return data |
|
end, |
|
stop = function(onboarding, data) |
|
data: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, |
|
} |
|
|
|
|
|
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 |
|
|
|
|
|
_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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
|
|
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 |
|
|
|
local responseData = JSON:Decode(res.Body) |
|
|
|
|
|
simulation.engineId = responseData.engine.id |
|
|
|
|
|
for _, loc in ipairs(responseData.locations) do |
|
simulation.locations[loc.name]._id = loc._id |
|
end |
|
|
|
|
|
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 |
|
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) |
|
|
|
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 = 40 |
|
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 |
|
|
|
|
|
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 spawnSquirrelAbovePlayer(player) |
|
local squirrel = Shape(Items.pratamacam.squirrel) |
|
squirrel:SetParent(World) |
|
squirrel.Position = player.Position + Number3(0, 20, 0) |
|
|
|
squirrel.LocalScale = 0.5 |
|
|
|
squirrel.Physics = PhysicsMode.Dynamic |
|
|
|
squirrel.Rotation = { 0, math.pi * 0.5, 0 } |
|
|
|
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) |
|
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) |
|
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) |
|
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) |
|
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) |
|
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) |
|
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) |
|
|
|
npc.avatar.IsHidden = true |
|
Timer(5, function() |
|
dialog:create("Aaaaand... I'm back!", npc.avatar) |
|
npc.avatar.IsHidden = false |
|
end) |
|
end, |
|
action_format_str = "{protagonist_name} exploded!", |
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{ |
|
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"):apply(obj, { includeRoot = true }, function(o) |
|
o.Physics = PhysicsMode.Disabled |
|
end) |
|
Player:EquipHat(obj) |
|
end) |
|
dialog:create("Let's get the party started!", npc.avatar) |
|
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) |
|
|
|
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() |
|
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, |
|
dist = 750, |
|
}) |
|
|
|
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) |
|
|
|
Player.Avatar:load({ usernameOrId = "aduermael" }) |
|
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 |
|
|
|
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 |
|
|