|
Modules = { |
|
gigax = "github.com/GigaxGames/integrations/cubzh:cdfd9a2", |
|
pathfinding = "github.com/caillef/cubzh-library/pathfinding:f8c4315", |
|
} |
|
|
|
Config = { |
|
Items = { "pratamacam.squirrel" }, |
|
} |
|
|
|
|
|
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 |
|
|
|
function generateWorld() |
|
local nbIslands = 20 |
|
local minSize = 4 |
|
local maxSize = 7 |
|
local dist = 750 |
|
local safearea = 200 |
|
floating_islands_generator:onReady(function() |
|
for i = 1, nbIslands do |
|
local island = floating_islands_generator:create(math.random(minSize, maxSize)) |
|
island:SetParent(World) |
|
island.Scale = Map.Scale |
|
island.Physics = PhysicsMode.Disabled |
|
local x = math.random(-dist, dist) |
|
local z = math.random(-dist, dist) |
|
while (x >= -safearea and x <= safearea) and (z >= -safearea and z <= safearea) do |
|
x = math.random(-dist, dist) |
|
z = math.random(-dist, dist) |
|
end |
|
island.Position = { |
|
x + (Map.Width * 0.5) * Map.Scale.X, |
|
math.random(300) - 150, |
|
z + (Map.Depth * 0.5) * Map.Scale.Z, |
|
} |
|
local t = x + z |
|
LocalEvent:Listen(LocalEvent.Name.Tick, function(dt) |
|
t = t + dt |
|
island.Position.Y = island.Position.Y + math.sin(t) * 0.02 |
|
end) |
|
end |
|
end) |
|
end |
|
|
|
Client.OnWorldObjectLoad = function(obj) |
|
print("OBJECT DID LOAD:", obj.Name) |
|
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() |
|
|
|
|
|
local test = require("avatar"):get("aduermael") |
|
test:SetParent(World) |
|
test.Position:Set(obj.Position) |
|
test.Rotation:Set(obj.Rotation) |
|
test.Scale:Set(obj.Scale) |
|
|
|
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() |
|
|
|
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() |
|
|
|
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 |
|
generateWorld() |
|
|
|
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) |
|
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, |
|
}) |
|
end |
|
|
|
|
|
|
|
floating_islands_generator = {} |
|
|
|
local cachedTree |
|
|
|
local COLORS = { |
|
GRASS = Color(19, 133, 16), |
|
DIRT = Color(107, 84, 40), |
|
STONE = Color.Grey, |
|
} |
|
|
|
local function islandHeight(x, z, radius) |
|
local distance = math.sqrt(x * x + z * z) |
|
local normalizedDistance = distance / radius |
|
local maxy = -((1 + radius) * 2 - (normalizedDistance ^ 4) * distance) |
|
return maxy |
|
end |
|
|
|
floating_islands_generator.onReady = function(_, callback) |
|
Object:Load("knosvoxel.oak_tree", function(obj) |
|
cachedTree = obj |
|
callback() |
|
end) |
|
end |
|
|
|
floating_islands_generator.create = function(_, radius) |
|
local shape = MutableShape() |
|
shape.Pivot = { 0.5, 0.5, 0.5 } |
|
for z = -radius, radius do |
|
for x = -radius, radius do |
|
local maxy = islandHeight(x, z, radius) |
|
shape:AddBlock(COLORS.DIRT, x, -2, z) |
|
shape:AddBlock(COLORS.GRASS, x, -1, z) |
|
shape:AddBlock(COLORS.GRASS, x, 0, z) |
|
if maxy <= -3 then |
|
shape:AddBlock(COLORS.DIRT, x, -3, z) |
|
end |
|
for y = maxy, -3 do |
|
shape:AddBlock(COLORS.STONE, x, y, z) |
|
end |
|
end |
|
end |
|
|
|
xShift = math.random(-radius, radius) |
|
zShift = math.random(-radius, radius) |
|
for z = -radius, radius do |
|
for x = -radius, radius do |
|
local maxy = islandHeight(x, z, radius) - 2 |
|
shape:AddBlock(COLORS.DIRT, x + xShift, -2 + 2, z + zShift) |
|
shape:AddBlock(COLORS.GRASS, x + xShift, -1 + 2, z + zShift) |
|
shape:AddBlock(COLORS.GRASS, x + xShift, 0 + 2, z + zShift) |
|
if maxy <= -3 + 2 then |
|
shape:AddBlock(COLORS.DIRT, x + xShift, -3 + 2, z + zShift) |
|
end |
|
for y = maxy, -3 + 2 do |
|
shape:AddBlock(COLORS.STONE, x + xShift, y, z + zShift) |
|
end |
|
end |
|
end |
|
|
|
for i = 1, math.random(1, 2) do |
|
local obj = Shape(cachedTree, { includeChildren = true }) |
|
obj.Position = { 0, 0, 0 } |
|
local box = Box() |
|
box:Fit(obj, true) |
|
obj.Pivot = Number3(obj.Width / 2, box.Min.Y + obj.Pivot.Y + 4, obj.Depth / 2) |
|
obj:SetParent(shape) |
|
require("hierarchyactions"):applyToDescendants(obj, { includeRoot = true }, function(o) |
|
o.Physics = PhysicsMode.Disabled |
|
end) |
|
local coords = Number3(math.random(-radius + 1, radius - 1), 0, math.random(-radius + 1, radius - 1)) |
|
while shape:GetBlock(coords) do |
|
coords.Y = coords.Y + 1 |
|
end |
|
obj.Scale = math.random(70, 150) / 1000 |
|
obj.Rotation.Y = math.random(1, 4) * math.pi * 0.25 |
|
obj.LocalPosition = coords |
|
end |
|
|
|
return shape |
|
end |
|
|