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