Corentin Cailleaud commited on
Commit
3e666d4
1 Parent(s): 6cc7412
Files changed (1) hide show
  1. cubzh.lua +455 -1
cubzh.lua CHANGED
@@ -1,7 +1,7 @@
1
  math.randomseed(math.floor(Time.UnixMilli() % 100000))
2
 
3
  Modules = {
4
- gigax = "github.com/GigaxGames/integrations/cubzh:5025b99",
5
  pathfinding = "github.com/caillef/cubzh-library/pathfinding:f8c4315",
6
  floating_island_generator = "github.com/caillef/cubzh-library/floating_island_generator:82d22a5",
7
  }
@@ -10,6 +10,460 @@ Config = {
10
  Items = { "pratamacam.squirrel" },
11
  }
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  -- Function to spawn a squirrel above the player
14
  function spawnSquirrelAbovePlayer(player)
15
  local squirrel = Shape(Items.pratamacam.squirrel)
 
1
  math.randomseed(math.floor(Time.UnixMilli() % 100000))
2
 
3
  Modules = {
4
+ --gigax = "github.com/GigaxGames/integrations/cubzh:5025b99",
5
  pathfinding = "github.com/caillef/cubzh-library/pathfinding:f8c4315",
6
  floating_island_generator = "github.com/caillef/cubzh-library/floating_island_generator:82d22a5",
7
  }
 
10
  Items = { "pratamacam.squirrel" },
11
  }
12
 
13
+ local easy_onboarding = {}
14
+
15
+ local currentStep = 0
16
+ local steps = {}
17
+ local stopCallbackData
18
+
19
+ easy_onboarding.startOnboarding = function(self, config)
20
+ currentStep = 1
21
+ steps = config.steps
22
+ stopCallbackData = steps[1].start(currentStep, self)
23
+ end
24
+
25
+ easy_onboarding.next = function(self)
26
+ steps[currentStep].stop(self, stopCallbackData)
27
+ currentStep = currentStep + 1
28
+ stopCallbackData = steps[currentStep].start(self, currentStep)
29
+ end
30
+
31
+ local onboarding_config = {
32
+ steps = {
33
+ {
34
+ start = function(onboarding, step)
35
+ local ui = require("uikit")
36
+ local data = {}
37
+ data.ui = ui:createText("Hold click and drag to move camera")
38
+ data.ui.pos = { Screen.Width * 0.5 - data.ui.Width * 0.5, Screen.Height * 0.5 - data.ui.Height * 0.5 }
39
+ return data
40
+ end,
41
+ stop = function(onboarding, data)
42
+ data:remove()
43
+ end,
44
+ },
45
+ {
46
+ start = function(onboarding, step)
47
+ local ui = require("uikit")
48
+ local data = {}
49
+ data.ui = ui:createText("Use WASD/ZQSD to move")
50
+ data.ui.pos = { Screen.Width * 0.5 - data.ui.Width * 0.5, Screen.Height * 0.5 - data.ui.Height * 0.5 }
51
+ return data
52
+ end,
53
+ stop = function(onboarding, data)
54
+ data:remove()
55
+ end,
56
+ },
57
+ },
58
+ }
59
+
60
+ Config = {
61
+ Items = { "pratamacam.squirrel" },
62
+ }
63
+
64
+ local gigax = {}
65
+
66
+ local CUBZH_API_TOKEN =
67
+ "H4gjL-e9kvLF??2pz6oh=kJL497cBnsyCrQFdVkFadUkLnIaEamroYHb91GywMXrbGeDdmTiHxi8EqmJduCKPrDnfqWsjGuF0JJCUTrasGcBfGx=tlJCjq5q8jhVHWL?krIE74GT9AJ7qqX8nZQgsDa!Unk8GWaqWcVYT-19C!tCo11DcLvrnJPEOPlSbH7dDcXmAMfMEf1ZwZ1v1C9?2/BjPDeiAVTRlLFilwRFmKz7k4H-kCQnDH-RrBk!ZHl7"
68
+ local API_URL = "https://gig.ax"
69
+
70
+ local TRIGGER_AREA_SIZE = Number3(60, 30, 60)
71
+
72
+ local headers = {
73
+ ["Content-Type"] = "application/json",
74
+ ["Authorization"] = CUBZH_API_TOKEN,
75
+ }
76
+
77
+ -- HELPERS
78
+ local _helpers = {}
79
+
80
+ _helpers.lookAt = function(obj, target)
81
+ if not target then
82
+ require("ease"):linear(obj, 0.1).Forward = obj.initialForward
83
+ obj.Tick = nil
84
+ return
85
+ end
86
+ obj.Tick = function(self, _)
87
+ _helpers.lookAtHorizontal(self, target)
88
+ end
89
+ end
90
+
91
+ _helpers.lookAtHorizontal = function(o1, o2)
92
+ local n3_1 = Number3.Zero
93
+ local n3_2 = Number3.Zero
94
+ n3_1:Set(o1.Position.X, 0, o1.Position.Z)
95
+ n3_2:Set(o2.Position.X, 0, o2.Position.Z)
96
+ require("ease"):linear(o1, 0.1).Forward = n3_2 - n3_1
97
+ end
98
+
99
+ -- Function to calculate distance between two positions
100
+ _helpers.calculateDistance = function(_, pos1, pos2)
101
+ local dx = pos1.X - pos2.X
102
+ local dy = pos1.Y - pos2.Y
103
+ local dz = pos1.Z - pos2.Z
104
+ return math.sqrt(dx * dx + dy * dy + dz * dz)
105
+ end
106
+
107
+ _helpers.findClosestLocation = function(_, position, locationData)
108
+ if not locationData then
109
+ return
110
+ end
111
+ local closestLocation = nil
112
+ local smallestDistance = math.huge -- Large initial value
113
+
114
+ for _, location in pairs(locationData) do
115
+ local distance = _helpers:calculateDistance(
116
+ position,
117
+ Map:WorldToBlock(Number3(location.position.x, location.position.y, location.position.z))
118
+ )
119
+ if distance < smallestDistance then
120
+ smallestDistance = distance
121
+ closestLocation = location
122
+ end
123
+ end
124
+ -- Closest location found, now send its ID to update the character's location
125
+ return closestLocation
126
+ end
127
+
128
+ if IsClient then
129
+ local simulation = {}
130
+
131
+ local npcDataClientById = {}
132
+ local waitingLinkNPCs = {}
133
+ local skillCallbacks = {}
134
+
135
+ local gigaxHttpClient = {}
136
+ gigaxHttpClient.registerMainCharacter = function(_, engineId, locationId, callback)
137
+ local body = JSON:Encode({
138
+ name = Player.Username,
139
+ physical_description = "A human playing the game",
140
+ current_location_id = locationId,
141
+ position = { x = 0, y = 0, z = 0 },
142
+ })
143
+ local apiUrl = API_URL .. "/api/character/company/main?engine_id=" .. engineId
144
+ HTTP:Post(apiUrl, headers, body, function(response)
145
+ if response.StatusCode ~= 200 then
146
+ print("Error creating or fetching main character: " .. response.StatusCode)
147
+ return
148
+ end
149
+ callback(response.Body)
150
+ end)
151
+ end
152
+
153
+ gigaxHttpClient.stepMainCharacter = function(_, engineId, characterId, skill, content, npcName, npcId, callback)
154
+ if not engineId then
155
+ return
156
+ end
157
+ local stepUrl = API_URL .. "/api/character/" .. characterId .. "/step-no-ws?engine_id=" .. engineId
158
+ local body = JSON:Encode({
159
+ character_id = characterId,
160
+ skill = skill,
161
+ target_name = npcName,
162
+ target = npcId,
163
+ content = content,
164
+ })
165
+ HTTP:Post(stepUrl, headers, body, function(response)
166
+ if response.StatusCode ~= 200 then
167
+ print("Error stepping character: " .. response.StatusCode)
168
+ return
169
+ end
170
+ callback(response.Body)
171
+ end)
172
+ end
173
+
174
+ gigaxHttpClient.updateCharacterPosition = function(_, engineId, characterId, locationId, position, callback)
175
+ local body = JSON:Encode({
176
+ current_location_id = locationId,
177
+ position = { x = position.X, y = position.Y, z = position.Z },
178
+ })
179
+ local apiUrl = API_URL .. "/api/character/" .. characterId .. "?engine_id=" .. engineId
180
+ HTTP:Post(apiUrl, headers, body, function(response)
181
+ if response.StatusCode ~= 200 then
182
+ print("Error updating character location: " .. response.StatusCode)
183
+ return
184
+ end
185
+ if callback then
186
+ callback(response.Body)
187
+ end
188
+ end)
189
+ end
190
+
191
+ local onEndData
192
+ local prevAction
193
+ local function npcResponse(actionData)
194
+ local currentAction = string.lower(actionData.skill.name)
195
+ if onEndData and skillCallbacks[prevAction].onEndCallback then
196
+ skillCallbacks[prevAction].onEndCallback(gigax, onEndData, currentAction)
197
+ end
198
+ local callback = skillCallbacks[currentAction].callback
199
+ prevAction = string.lower(actionData.skill.name)
200
+ if not callback then
201
+ return
202
+ end
203
+ onEndData = callback(gigax, actionData, simulation.config)
204
+ end
205
+
206
+ local function registerEngine(config)
207
+ local apiUrl = API_URL .. "/api/engine/company/"
208
+
209
+ simulation.locations = {}
210
+ simulation.NPCs = {}
211
+ simulation.config = config
212
+ simulation.player = Player
213
+
214
+ -- Prepare the data structure expected by the backend
215
+ local engineData = {
216
+ name = Player.UserID .. ":" .. config.simulationName,
217
+ description = config.simulationDescription,
218
+ NPCs = {},
219
+ locations = {},
220
+ radius,
221
+ }
222
+
223
+ for _, npc in pairs(config.NPCs) do
224
+ simulation.NPCs[npc.name] = {
225
+ name = npc.name,
226
+ physical_description = npc.physicalDescription,
227
+ psychological_profile = npc.psychologicalProfile,
228
+ initial_reflections = npc.initialReflections,
229
+ current_location_name = npc.currentLocationName,
230
+ skills = config.skills,
231
+ }
232
+ table.insert(engineData.NPCs, simulation.NPCs[npc.name])
233
+ end
234
+
235
+ for _, loc in ipairs(config.locations) do
236
+ simulation.locations[loc.name] = {
237
+ name = loc.name,
238
+ position = { x = loc.position.X, y = loc.position.Y, z = loc.position.Z },
239
+ description = loc.description,
240
+ }
241
+ table.insert(engineData.locations, simulation.locations[loc.name])
242
+ end
243
+
244
+ local body = JSON:Encode(engineData)
245
+ HTTP:Post(apiUrl, headers, body, function(res)
246
+ if res.StatusCode ~= 201 then
247
+ print("Error updating engine: " .. res.StatusCode)
248
+ return
249
+ end
250
+ -- Decode the response body to extract engine and location IDs
251
+ local responseData = JSON:Decode(res.Body)
252
+
253
+ -- Save the engine_id for future use
254
+ simulation.engineId = responseData.engine.id
255
+
256
+ -- Saving all the _ids inside locationData table:
257
+ for _, loc in ipairs(responseData.locations) do
258
+ simulation.locations[loc.name]._id = loc._id
259
+ end
260
+
261
+ -- same for characters:
262
+ for _, npc in pairs(responseData.NPCs) do
263
+ simulation.NPCs[npc.name]._id = npc._id
264
+ simulation.NPCs[npc.name].position = Number3(npc.position.x, npc.position.y, npc.position.z)
265
+ end
266
+
267
+ gigaxHttpClient:registerMainCharacter(
268
+ simulation.engineId,
269
+ simulation.locations[config.startingLocationName]._id,
270
+ function(body)
271
+ simulation.character = JSON:Decode(body)
272
+ for name, npc in pairs(waitingLinkNPCs) do
273
+ npc._id = simulation.NPCs[name]._id
274
+ npc.name = name
275
+ npcDataClientById[npc._id] = npc
276
+ end
277
+ Timer(1, true, function()
278
+ local position = Map:WorldToBlock(Player.Position)
279
+ gigax:updateCharacterPosition(simulation, simulation.character._id, position)
280
+ end)
281
+ end
282
+ )
283
+ end)
284
+ end
285
+
286
+ findTargetNpc = function(player)
287
+ if not simulation or type(simulation.NPCs) ~= "table" then
288
+ return
289
+ end
290
+
291
+ local closerDist = 1000
292
+ local closerNpc
293
+ for _, npc in pairs(simulation.NPCs) do
294
+ local dist = (npc.position - player.Position).Length
295
+ if closerDist > dist then
296
+ closerDist = dist
297
+ closerNpc = npc
298
+ end
299
+ end
300
+ if closerDist > 50 then
301
+ return
302
+ end -- max distance is 50
303
+ return closerNpc
304
+ end
305
+
306
+ gigax.action = function(_, data)
307
+ local npc = findTargetNpc(Player)
308
+ if not npc then
309
+ return
310
+ end
311
+
312
+ local content = data.content
313
+ data.content = nil
314
+ gigaxHttpClient:stepMainCharacter(
315
+ simulation.engineId,
316
+ simulation.character._id,
317
+ data,
318
+ content,
319
+ npc.name,
320
+ npc._id,
321
+ function(body)
322
+ local actions = JSON:Decode(body)
323
+ for _, action in ipairs(actions) do
324
+ npcResponse(action)
325
+ end
326
+ end
327
+ )
328
+ end
329
+
330
+ gigax.getNpc = function(_, id)
331
+ return npcDataClientById[id]
332
+ end
333
+
334
+ local skillOnAction = function(actionType, callback, onEndCallback)
335
+ skillCallbacks[actionType] = {
336
+ callback = callback,
337
+ onEndCallback = onEndCallback,
338
+ }
339
+ end
340
+
341
+ local prevSyncPosition
342
+ gigax.updateCharacterPosition = function(_, simulation, characterId, position)
343
+ if not simulation then
344
+ return
345
+ end
346
+ if position == prevSyncPosition then
347
+ return
348
+ end
349
+ prevSyncPosition = position
350
+ local closest = _helpers:findClosestLocation(position, simulation.locations)
351
+ if not closest then
352
+ print("can't update character position: no closest location found, id:", characterId, position)
353
+ return
354
+ end
355
+ if not characterId then
356
+ return
357
+ end
358
+ gigaxHttpClient:updateCharacterPosition(simulation.engineId, characterId, closest._id, position)
359
+ end
360
+
361
+ local function createNPC(name, currentPosition, rotation)
362
+ -- Create the NPC's Object and Avatar
363
+ local NPC = {}
364
+ NPC.object = Object()
365
+ World:AddChild(NPC.object)
366
+ NPC.object.Position = currentPosition or Number3(0, 0, 0)
367
+ NPC.object.Scale = 0.5
368
+ NPC.object.Physics = PhysicsMode.Trigger
369
+ NPC.object.CollisionBox = Box({
370
+ -TRIGGER_AREA_SIZE.Width * 0.5,
371
+ math.min(-TRIGGER_AREA_SIZE.Height, NPC.object.CollisionBox.Min.Y),
372
+ -TRIGGER_AREA_SIZE.Depth * 0.5,
373
+ }, {
374
+ TRIGGER_AREA_SIZE.Width * 0.5,
375
+ math.max(TRIGGER_AREA_SIZE.Height, NPC.object.CollisionBox.Max.Y),
376
+ TRIGGER_AREA_SIZE.Depth * 0.5,
377
+ })
378
+
379
+ local text = Text()
380
+ text.Text = string.upper(string.sub(name, 4, 4)) .. string.sub(name, 5, #name)
381
+ text:SetParent(NPC.object)
382
+ text.Type = TextType.Screen
383
+ text.IsUnlit = true
384
+ text.LocalPosition.Y = 36
385
+ text.FontSize = 40
386
+
387
+ NPC.object.OnCollisionBegin = function(self, other)
388
+ if other ~= Player then
389
+ return
390
+ end
391
+ _helpers.lookAt(self.avatarContainer, other)
392
+ end
393
+ NPC.object.OnCollisionEnd = function(self, other)
394
+ if other ~= Player then
395
+ return
396
+ end
397
+ _helpers.lookAt(self.avatarContainer, nil)
398
+ end
399
+
400
+ local container = Object()
401
+ container.Rotation:Set(rotation or Number3.Zero)
402
+ container.initialForward = container.Forward:Copy()
403
+ container:SetParent(NPC.object)
404
+ container.Physics = PhysicsMode.Trigger
405
+ NPC.object.avatarContainer = container
406
+
407
+ NPC.avatar = require("avatar"):get(name)
408
+ NPC.avatar:SetParent(NPC.object.avatarContainer)
409
+
410
+ NPC.name = name
411
+
412
+ NPC.object.onIdle = function()
413
+ local animations = NPC.avatar.Animations
414
+ NPC.object.avatarContainer.LocalRotation = { 0, 0, 0 }
415
+ if not animations or animations.Idle.IsPlaying then
416
+ return
417
+ end
418
+ if animations.Walk.IsPlaying then
419
+ animations.Walk:Stop()
420
+ end
421
+ animations.Idle:Play()
422
+ end
423
+
424
+ NPC.object.onMove = function()
425
+ local animations = NPC.avatar.Animations
426
+ NPC.object.avatarContainer.LocalRotation = { 0, 0, 0 }
427
+ if not animations or animations.Walk.IsPlaying then
428
+ return
429
+ end
430
+ if animations.Idle.IsPlaying then
431
+ animations.Idle:Stop()
432
+ end
433
+ animations.Walk:Play()
434
+ end
435
+
436
+ waitingLinkNPCs[name] = NPC
437
+
438
+ -- review this to update location and position
439
+ Timer(1, true, function()
440
+ if not simulation then
441
+ return
442
+ end
443
+ local position = Map:WorldToBlock(NPC.object.Position)
444
+ local prevPosition = NPC.object.prevSyncPosition
445
+ if prevPosition == position then
446
+ return
447
+ end
448
+ gigax:updateCharacterPosition(simulation, NPC._id, position)
449
+ NPC.object.prevSyncPosition = position
450
+ end)
451
+ return NPC
452
+ end
453
+
454
+ gigax.setConfig = function(_, config)
455
+ for _, elem in ipairs(config.skills) do
456
+ skillOnAction(string.lower(elem.name), elem.callback, elem.onEndCallback)
457
+ elem.callback = nil
458
+ elem.onEndCallback = nil
459
+ end
460
+ for _, elem in ipairs(config.NPCs) do
461
+ createNPC(elem.name, elem.position, elem.rotation)
462
+ end
463
+ registerEngine(config)
464
+ end
465
+ end
466
+
467
  -- Function to spawn a squirrel above the player
468
  function spawnSquirrelAbovePlayer(player)
469
  local squirrel = Shape(Items.pratamacam.squirrel)