Spaces:
No application file
No application file
| Modules = { | |
| gigax = "github.com/GigaxGames/integrations/cubzh:cdfd9a2", | |
| pathfinding = "github.com/caillef/cubzh-library/pathfinding:f8c4315", | |
| } | |
| Config = { | |
| Items = { "pratamacam.squirrel" }, | |
| } | |
| -- 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) | |
| 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) | |
| --print(string.format("%s: %s", npc.name, "EXPLODING")) | |
| 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 = "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) | |
| 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) | |
| 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"):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) | |
| -- 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 | |
| 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() | |
| -- DEBUG: this works, but not avatar loaded with gigax module | |
| 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() | |
| -- 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 | |
| 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 | |
| -- 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, | |
| }) | |
| end | |
| -- Module floating islands | |
| 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 | |