AEUPH/AI-NPC-VR
0
1math.randomseed(math.floor(Time.UnixMilli() % 100000))2 3Modules = {4 gigax = "github.com/GigaxGames/integrations/cubzh:9a71b9f",5 pathfinding = "github.com/caillef/cubzh-library/pathfinding:5f9c6bd",6 floating_island_generator = "github.com/caillef/cubzh-library/floating_island_generator:82d22a5",7 easy_onboarding = "github.com/caillef/cubzh-library/easy_onboarding:77728ee",8}9 10Config = {11 Items = { "pratamacam.squirrel" },12}13 14-- Function to spawn a squirrel above the player15function spawnSquirrelAbovePlayer(player)16 local squirrel = Shape(Items.pratamacam.squirrel)17 squirrel:SetParent(World)18 squirrel.Position = player.Position + Number3(0, 20, 0)19 -- make scale smaller20 squirrel.LocalScale = 0.521 -- remove collision22 squirrel.Physics = PhysicsMode.Dynamic23 -- rotate it 90 degrees to the right24 squirrel.Rotation = { 0, math.pi * 0.5, 0 }25 -- this would make squirrel.Rotation = player.Rotation26 World:AddChild(squirrel)27 return squirrel28end29 30local SIMULATION_NAME = "Islands" .. tostring(math.random())31local SIMULATION_DESCRIPTION = "Three floating islands."32 33 34local skills = {35 {36 name = "SAY",37 description = "Say smthg out loud",38 parameter_types = { "character", "content" },39 callback = function(client, action)40 local npc = client:getNpc(action.character_id)41 if not npc then42 print("Can't find npc")43 return44 end45 dialog:create(action.content, npc.avatar.Head)46 print(string.format("%s: %s", npc.gameName, action.content))47 end,48 action_format_str = "{protagonist_name} said '{content}' to {target_name}",49 },50 {51 name = "MOVE",52 description = "Move to a new location",53 parameter_types = { "location" },54 callback = function(client, action, config)55 local targetName = action.target_name56 local targetPosition = findLocationByName(targetName, config)57 if not targetPosition then58 print("tried to move to an unknown place", targetName)59 return60 end61 local npc = client:getNpc(action.character_id)62 dialog:create("I'm going to " .. targetName, npc.avatar.Head)63 print(string.format("%s: %s", npc.gameName, "I'm going to " .. targetName))64 local origin = Map:WorldToBlock(npc.object.Position)65 local destination = Map:WorldToBlock(targetPosition) + Number3(math.random(-1, 1), 0, math.random(-1, 1))66 local canMove = pathfinding:moveObjectTo(npc.object, origin, destination)67 if not canMove then68 dialog:create("I can't go there", npc.avatar.Head)69 return70 end71 end,72 action_format_str = "{protagonist_name} moved to {target_name}",73 },74 {75 name = "GREET",76 description = "Greet a character by waving your hand at them",77 parameter_types = { "character" },78 callback = function(client, action)79 local npc = client:getNpc(action.character_id)80 if not npc then81 print("Can't find npc")82 return83 end84 85 dialog:create("<Greets you warmly!>", npc.avatar.Head)86 print(string.format("%s: %s", npc.gameName, "<Greets you warmly!>"))87 88 npc.avatar.Animations.SwingRight:Play()89 end,90 action_format_str = "{protagonist_name} waved their hand at {target_name} to greet them",91 },92 {93 name = "JUMP",94 description = "Jump in the air",95 parameter_types = {},96 callback = function(client, action)97 local npc = client:getNpc(action.character_id)98 if not npc then99 print("Can't find npc")100 return101 end102 103 dialog:create("<Jumps in the air!>", npc.avatar.Head)104 print(string.format("%s: %s", npc.gameName, "<Jumps in the air!>"))105 106 npc.object.avatarContainer.Physics = PhysicsMode.Dynamic107 npc.object.avatarContainer.Velocity.Y = 50108 Timer(3, function()109 npc.object.avatarContainer.Physics = PhysicsMode.Trigger110 end)111 end,112 action_format_str = "{protagonist_name} jumped up in the air for a moment.",113 },114 {115 name = "FOLLOW",116 description = "Follow a character around for a while",117 parameter_types = { "character" },118 callback = function(client, action)119 local npc = client:getNpc(action.character_id)120 if not npc then121 print("Can't find npc")122 return123 end124 125 dialog:create("I'm following you", npc.avatar.Head)126 print(string.format("%s: %s", npc.gameName, "I'm following you"))127 128 followHandler = pathfinding:followObject(npc.object, Player)129 return {130 followHandler = followHandler,131 }132 end,133 onEndCallback = function(_, data)134 data.followHandler:Stop()135 end,136 action_format_str = "{protagonist_name} followed {target_name} for a while.",137 },138 {139 name = "FIRECRACKER",140 description = "Perform a fun, harmless little explosion to make people laugh!",141 parameter_types = { "character" },142 callback = function(client, action)143 local npc = client:getNpc(action.character_id)144 if not npc then145 print("Can't find npc")146 return147 end148 149 require("explode"):shapes(npc.avatar)150 dialog:create("*boom*", npc.avatar.Head)151 npc.avatar.IsHidden = true152 Timer(5, function()153 dialog:create("Aaaaand... I'm back!", npc.avatar.Head)154 npc.avatar.IsHidden = false155 end)156 end,157 action_format_str = "{protagonist_name} exploded like a firecracker, with a bang!",158 },--[[159 {160 name = "GIVEAPPLE",161 description = "Give a pice of bread (or a baguette) to someone",162 parameter_types = {"character"},163 callback = function(client, action)164 local npc = client:getNpc(action.character_id)165 if not npc then print("Can't find npc") return end166 local shape = MutableShape()167 shape:AddBlock(Color.Red, 0, 0, 0)168 shape.Scale = 4169 Player:EquipRightHand(shape)170 dialog:create("Here is an apple for you!", npc.avatar.Head)171 end,172 action_format_str = "{protagonist_name} gave you a piece of bread!"173 }, --]]174 {175 name = "GIANT",176 description = "Double your height to become a giant for a few seconds.",177 parameter_types = {"character"},178 callback = function(client, action)179 local npc = client:getNpc(action.character_id)180 if not npc then print("Can't find npc") return end181 182 npc.object.Scale = npc.object.Scale * 2183 dialog:create("I am taller than you now!", npc.avatar.Head)184 end,185 action_format_str = "{protagonist_name} doubled his height!"186 },187 {188 name = "GIVEHAT",189 description = "Give a party hat to someone",190 parameter_types = { "character" },191 callback = function(client, action)192 local npc = client:getNpc(action.character_id)193 if not npc then194 print("Can't find npc")195 return196 end197 198 Object:Load("claire.party_hat", function(obj)199 require("hierarchyactions"):applyToDescendants(obj, { includeRoot = true }, function(o)200 o.Physics = PhysicsMode.Disabled201 end)202 Player:EquipHat(obj)203 end)204 dialog:create("Let's get the party started!", npc.avatar.Head)205 end,206 action_format_str = "{protagonist_name} gave you a piece of bread!",207 },208 {209 name = "FLYINGSQUIRREL",210 description = "Summon a flying squirrel - only the scientist can do this!!",211 parameter_types = {},212 callback = function(client, action)213 local npc = client:getNpc(action.character_id)214 if not npc then215 print("Can't find npc")216 return217 end218 219 local squirrel = spawnSquirrelAbovePlayer(Player)220 dialog:create("Wooh, squirrel!", npc.avatar.Head)221 -- make it disappear after a while222 Timer(5, function()223 squirrel:RemoveFromParent()224 squirrel = nil225 end)226 end,227 action_format_str = "{protagonist_name} summoned a flying squirrel! It's vibrating with excitement!",228 },229}230 231local locations = {232 {233 name = "Scientist Island",234 description = "A small island with a scientist and its pet chilling.",235 },236 {237 name = "Baker Island",238 description = "A small bakery on a floating island in the sky.",239 },240 {241 name = "Pirate Island",242 description = "A small floating island in the sky with a pirate and its ship.",243 },244 {245 name = "Center",246 description = "Center point between the three islands.",247 },248}249 250local NPCs = {251 {252 name = "npcscientist",253 gameName = "Scientist",254 physicalDescription = "Short, with a stern expression and sharp eyes",255 psychologicalProfile = "Grumpy but insightful, this scientist loves to surprise his guests with cool tricks! For example, he frequently lights up little firecrackers to create harmless and fun explosions.",256 currentLocationName = "Scientist Island",257 initialReflections = {258 "I just arrived on this island to feed my pet, he loves tulips so much.",259 "Yesterday, you were visited by a newcomer. He asked you to perform fun tricks like increase your size or create little explosions - you complied merrily!",260 "I am a scientist that works on new pets for everyone, so that each individual can have the pet of their dreams",261 "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.",262 },263 },264 {265 name = "npcbaker",266 gameName = "Baker",267 physicalDescription = "Tall, with a solemn demeanor and thoughtful eyes",268 psychologicalProfile = "Wise and mysterious, calm under pressure",269 currentLocationName = "Baker Island",270 initialReflections = {271 "I am a baker and I make food for everyone that pass by.",272 "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.",273 "I love living here on these floating islands, the view is amazing from my wind mill.",274 "I like to talk to strangers like the pirate that just arrived or the scientist coming time to time to feed his pet.",275 },276 },277 {278 name = "npcpirate",279 gameName = "Pirate",280 physicalDescription = "Average height, with bright green eyes and a warm smile",281 psychologicalProfile = "Friendly and helpful, quick-witted and resourceful",282 currentLocationName = "Pirate Island",283 initialReflections = {284 "Ahoy, matey! I'm Captain Ruby Storm, a fearless lass from the seven skies.",285 "I've docked me floating ship on this here floating isle to sell me wares (almost legally) retrieved treasures from me last daring adventure.",286 "So, who be lookin' to trade with a swashbuckler like meself?",287 },288 },289}290 291local gigaxWorldConfig = {292 simulationName = SIMULATION_NAME,293 simulationDescription = SIMULATION_DESCRIPTION,294 startingLocationName = "Center",295 skills = skills,296 locations = locations,297 NPCs = NPCs,298}299 300findLocationByName = function(targetName, config)301 for _, node in ipairs(config.locations) do302 if string.lower(node.name) == string.lower(targetName) then303 return node.position304 end305 end306end307 308Client.OnWorldObjectLoad = function(obj)309 if obj.Name == "pirate_ship" then310 obj.Scale = 1311 end312 313 local locationsIndexByName = {}314 for k, v in ipairs(gigaxWorldConfig.locations) do315 locationsIndexByName[v.name] = k316 end317 local npcIndexByName = {318 NPC_scientist = 1,319 NPC_baker = 2,320 NPC_pirate = 3,321 }322 323 local index = npcIndexByName[obj.Name]324 if index then325 local pos = obj.Position:Copy()326 gigaxWorldConfig.NPCs[index].position = pos327 gigaxWorldConfig.NPCs[index].rotation = obj.Rotation:Copy()328 329 local locationName = gigaxWorldConfig.NPCs[index].currentLocationName330 local locationIndex = locationsIndexByName[locationName]331 gigaxWorldConfig.locations[locationIndex].position = pos332 obj:RemoveFromParent()333 end334end335 336Client.OnStart = function()337 easy_onboarding:startOnboarding(onboardingConfig)338 339 require("object_skills").addStepClimbing(Player, {340 mapScale = MAP_SCALE,341 collisionGroups = Map.CollisionGroups,342 })343 344 gigaxWorldConfig.locations[4].position = Number3(Map.Width * 0.5, Map.Height - 2, Map.Depth * 0.5) * Map.Scale345 346 floating_island_generator:generateIslands({347 nbIslands = 20,348 minSize = 4,349 maxSize = 7,350 safearea = 200, -- min dist of islands from 0,0,0351 dist = 750, -- max dist of islands352 })353 354 local ambience = require("ambience")355 ambience:set(ambience.dusk)356 357 sfx = require("sfx")358 Player.Head:AddChild(AudioListener)359 360 dropPlayer = function()361 Player.Position = Number3(Map.Width * 0.5, Map.Height + 10, Map.Depth * 0.5) * Map.Scale362 Player.Rotation = { 0, 0, 0 }363 Player.Velocity = { 0, 0, 0 }364 end365 World:AddChild(Player)366 dropPlayer()367 368 dialog = require("dialog")369 dialog:setMaxWidth(400)370 371 pathfinding:createPathfindingMap()372 373 gigax:setConfig(gigaxWorldConfig)374 375 local randomNames = { "aduermael", "soliton", "gdevillele", "caillef", "voxels", "petroglyph" }376 Player.Avatar:load({ usernameOrId = randomNames[math.random(#randomNames)] })377end378 379Client.Action1 = function()380 if Player.IsOnGround then381 sfx("hurtscream_1", { Position = Player.Position, Volume = 0.4 })382 Player.Velocity.Y = 100383 if Player.Motion.X == 0 and Player.Motion.Z == 0 then384 -- only play jump action when jumping without moving to avoid wandering around to trigger NPCs385 gigax:action({386 name = "JUMP",387 description = "Jump in the air",388 parameter_types = {},389 action_format_str = "{protagonist_name} jumped up in the air for a moment.",390 })391 end392 end393end394 395Client.Tick = function(dt)396 if Player.Position.Y < -500 then397 dropPlayer()398 end399end400 401Client.OnChat = function(payload)402 local msg = payload.message403 404 Player:TextBubble(msg, 3, true)405 sfx("waterdrop_2", { Position = Player.Position, Pitch = 1.1 + math.random() * 0.5 })406 407 gigax:action({408 name = "SAY",409 description = "Say smthg out loud",410 parameter_types = { "character", "content" },411 action_format_str = "{protagonist_name} said '{content}' to {target_name}",412 content = msg,413 })414 415 print("User: " .. payload.message)416 return true417end418 419onboardingConfig = {420 steps = {421 {422 start = function(onboarding)423 local data = {}424 data.ui = onboarding:createTextStep("1/3 - Hold click and drag to move the camera.")425 data.listener = LocalEvent:Listen(LocalEvent.Name.PointerDrag, function()426 Timer(1, function()427 onboarding:next()428 end)429 data.listener:Remove()430 end)431 return data432 end,433 stop = function(_, data)434 data.ui:remove()435 end,436 },437 {438 start = function(onboarding)439 local data = {}440 data.ui = onboarding:createTextStep("2/3 - Use WASD/ZQSD to move.")441 data.listener = LocalEvent:Listen(LocalEvent.Name.KeyboardInput, function()442 Timer(1, function()443 onboarding:next()444 end)445 data.listener:Remove()446 end)447 448 return data449 end,450 stop = function(_, data)451 data.ui:remove()452 end,453 },454 {455 start = function(onboarding)456 local data = {}457 data.ui = onboarding:createTextStep("3/3 - Press Enter in front of the Pirate to chat.")458 Timer(10, function()459 onboarding:next()460 end)461 return data462 end,463 stop = function(_, data)464 data.ui:remove()465 end,466 },467 },468}