-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbox2dxt-kit.livecodescript
More file actions
4400 lines (4109 loc) · 172 KB
/
Copy pathbox2dxt-kit.livecodescript
File metadata and controls
4400 lines (4109 loc) · 172 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
-- =====================================================================
-- box2dxt-kit.livecodescript · "Box2Dxt Kit" (b2k...)
--
-- A friendly, pure-xTalk toolkit over the box2dxt extension. You work in
-- PIXELS, SCREEN coordinates and DEGREES; the kit converts to Box2D's
-- metres / radians / y-up for you, runs the world, and moves your controls.
-- Runs in OpenXTalk (OXT); compatible with LiveCode 9.6.3+.
-- Requires the box2dxt extension loaded (put b2Version() should return 4).
--
-- ===================== 60-SECOND START =============================
-- on openCard
-- b2kQuickStart -- world + gravity + walls around the card + go
-- b2kSpawnBall 200,80, 50 -- create + drop a ball
-- b2kSpawnBox 260,80, 60,40, "orange" -- (read `the result` for the ref)
-- b2kContactTarget the long id of me -- (optional) collision messages
-- end openCard
-- on mouseDown ; get b2kGrab(the mouseH,the mouseV) ; end mouseDown
-- on mouseUp ; b2kRelease ; end mouseUp
-- on closeCard ; b2kStop ; end closeCard
--
-- ===================== CHEAT SHEET ================================
-- WORLD b2kSetup [gx,gy] · b2kQuickStart [gy] · b2kStart · b2kStop ·
-- b2kTeardown · b2kAddWalls · b2kWall x1,y1,x2,y2 · b2kClear ·
-- b2kPause · b2kResume · b2kIsRunning() · b2kKillFloor y
-- (movers below y are removed; "b2kFell ctrl" precedes each)
-- CONFIG b2kSetScale px · b2kSetOrigin x,y · b2kSetGravity gx,gy ·
-- b2kSetSubsteps n · b2kContactTarget obj · b2kFrameTarget obj ·
-- b2kEnableSleeping flag · b2kEnableContinuous flag
-- ATTACH b2kAddBox ctrl[,dyn] · b2kAddBall ctrl[,dyn] · b2kAddCapsule ctrl[,dyn]
-- b2kAddPolygon ctrl[,dyn] · b2kAddStatic ctrl · b2kAddGround [screenY]
-- SPAWN b2kSpawnBox x,y,w,h[,color] · b2kSpawnBall x,y,diam[,color] ·
-- b2kSpawnCapsule x,y,len,thick[,color] (-> control)
-- MATERIAL b2kSetBounce ctrl,0..1 · b2kSetFriction ctrl,0..1 · b2kSetDensity ctrl,d
-- BODY b2kSetBullet ctrl,flag · b2kSetFixedRotation ctrl,flag ·
-- b2kSetGravityScale ctrl,s · b2kSetDamping ctrl,lin[,ang] · b2kWake ctrl ·
-- b2kSetType ctrl,name · b2kSetStatic/Dynamic/Kinematic ctrl ·
-- b2kEnable/Disable ctrl · b2kSetSleepEnabled ctrl,flag
-- ACT b2kPush ctrl,dvx,dvy · b2kImpulse ctrl,ix,iy · b2kForce ctrl,fx,fy ·
-- b2kSetVelocity ctrl,vx,vy · b2kSpin ctrl,deg/s · b2kSpinBy ctrl,deg/s ·
-- b2kTorque ctrl,t · b2kAngularImpulse ctrl,imp · b2kMoveTo ctrl,x,y[,deg] ·
-- b2kExplode x,y[,radius][,power] · b2kRemove ctrl
-- GET b2kBodyOf(ctrl) · b2kPosition(ctrl) · b2kWorldCenter(ctrl) · b2kVelocity(ctrl) ·
-- b2kSpeed(ctrl) · b2kAngle(ctrl) · b2kMass(ctrl) · b2kBodyType(ctrl) ·
-- b2kGravityScale(ctrl) · b2kDamping(ctrl) · b2kIsAwake(ctrl) · b2kControlAt(x,y) ·
-- b2kControlContains(ctrl,x,y) · b2kBodyCount() · b2kAwakeCount() ·
-- b2kToWorldX/Y(px) · b2kToScreenX/Y(m) ·
-- b2kRayHit(x1,y1,x2,y2) + b2kRayHitX/Y() · b2kRayHitNormalX/Y() · b2kRayDist()
-- JOINTS b2kHinge ctrlA,ctrlB,x,y · b2kWeld ctrlA,ctrlB · b2kRope ctrlA,ctrlB[,len]
-- b2kSlider ctrlA,ctrlB,axisDeg · b2kWheel chassis,wheel,x,y[,axisDeg]
-- b2kMotor j,degPerSec[,maxTorque] · b2kSliderMotor j,pxPerSec[,maxForce] ·
-- b2kWheelMotor j,degPerSec[,maxTorque] · b2kRemoveJoint j
-- JOINT+ b2kHingeLimit j,loDeg,hiDeg · b2kHingeAngle(j) · b2kSliderLimit j,loPx,hiPx ·
-- b2kSliderPos(j) · b2kRopeRange j,minPx,maxPx · b2kRopeLength(j) ·
-- b2kSpring j,hz[,damp] · b2kWeldSpring j,hz[,damp] · b2kWheelSpring j,hz[,damp]
-- DRAG b2kGrab(x,y) -> control · b2kRelease
-- INPUT b2kInputOn/Off · b2kKeyIsDown(k) · b2kKeyPressed/Released(k) ·
-- b2kInputInject keys / b2kInputInjectOff (scripted keys: tests/replays) ·
-- b2kKeysHeld() · b2kBindAction name,keys · b2kActionIsDown/Pressed/
-- Released(name) · b2kBindAxis name,negKeys,posKeys · b2kAxis(name) ·
-- b2kFrameMS() (poll-based: arm b2kInputOn, read from on b2kFrame)
-- SPRITE b2kSheetLoad name,path,fw,fh[,n,margin,gap] · b2kSheetLoadAtlas name,png[,xml] ·
-- b2kSheetFromImage name,img,fw,fh[,n,margin,gap] · b2kSheetFrames(name) ·
-- b2kSheetAddFrame sheet,frame,x,y,w,h (no-XML packed sheets; fw/fh 0 = no grid) ·
-- b2kSheetFrameNames(name) · b2kSheetHasFrame(name,frame) ·
-- b2kSheetScale name,factor · b2kSheetFrameSize(name,frame) ·
-- b2kAnimDef sheet,anim,frames,fps[,loop] · b2kSpriteNew sheet[,frame,x,y] ·
-- b2kSpriteFromGIF path[,x,y] · b2kSpritePlay spr,anim[,restart] ·
-- b2kSpriteStop spr · b2kSpriteSetFrame spr,f · b2kSpriteFlipH spr,flag ·
-- b2kSpriteFPS spr,fps · b2kSpriteOnFinish spr,msg ·
-- b2kSpriteBind spr,bodyCtrl[,dx,dy] · b2kSpriteRemove spr
-- PLAYER b2kPlayerMake x,y,w,h[,sheet] · b2kPlayerAttach ctrl ·
-- b2kPlayerAnims idle,run,jump,fall[,land] · b2kPlayerSet key,value ·
-- b2kPlayerGet(key) · b2kPlayerOnGround() · b2kPlayerState() ·
-- b2kPlayerFacing() · b2kPlayerJump [speed] · b2kPlayerControl flag ·
-- b2kPlayer() · b2kPlayerSprite() · b2kPlayerRemove
-- (drives axis "moveX" + action "jump"; rebind those to remap)
-- CAMERA b2kCamOn [rect] · b2kCamOff · b2kCamFollow ctrl[,lerp] · b2kCamUnfollow ·
-- b2kCamDeadzone w,h · b2kCamBounds x1,y1,x2,y2 · b2kCamGoto x,y ·
-- b2kCamPos() · b2kCamShake amp,ms · b2kCamMouseX/Y() · b2kCamGroup() ·
-- b2kCamAdopt ctrl (world px == control locs; spawns auto-join the view)
-- AUDIO b2kSoundLoad name,path · b2kToneMake name,freqs,msPerNote[,vol,shape] ·
-- b2kSound name · b2kSoundLoop name · b2kSoundStop · b2kSoundMute flag ·
-- b2kSoundMuted() · b2kSoundVolume pct · b2kSoundIsLoaded(name) ·
-- b2kSoundStatus() (audioClips: one at a time; b2kToneMake = no asset files)
-- EVENTS on b2kContact pCtrlA,pCtrlB · on b2kEndContact pCtrlA,pCtrlB
-- (long ids; empty for walls/ground) · on b2kFrame (once per frame)
-- poll instead: b2kContactCount()+b2kContactA/B(i) · b2kEndContactCount()+…
-- SENSOR b2kAddSensor ctrl[,shape] · on b2kSensorEnter pSensor,pVisitor ·
-- on b2kSensorExit pSensor,pVisitor · b2kSensorCount()+EnterSensor/Visitor(i)
-- FILTER b2kDefineLayer name · b2kSetCategory ctrl,layers · b2kSetMask ctrl,layers ·
-- b2kSetCollisionGroup ctrl,n · b2kNoCollide ctrlA,ctrlB
-- TERRAIN b2kChain pointList[,loop] · b2kSmoothGround pointList (>=4 screen points)
-- QUERY+ b2kOverlap x1,y1,x2,y2 · b2kOverlapMoving x1,y1,x2,y2 (presence:
-- statics filtered out) · b2kOverlapCircle x,y,r · b2kRayHitAll x1,y1,x2,y2
-- MOTOR b2kMotorTo mover,ref,dxPx,dyPx,deg[,maxF,maxT] · b2kExplode (native blast)
-- TUNE b2kSetRestitutionThreshold px/s · b2kSetContactTuning hz,damp,pushPx ·
-- b2kSetJointTuning hz,damp · b2kSetMaxSpeed px/s · b2kEnableWarmStarting f ·
-- b2kProfile() · b2kAwakeBodyCount()
--
-- Tips: pass controls by reference (the long id of ... is safest). Keep moving
-- objects a sensible on-screen size (default scale 40 px/m, so ~4-400 px).
-- Graphic boxes/polygons and dynamic images rotate; buttons/fields/etc follow
-- position upright (their rotation is locked so the sim matches the render).
-- =====================================================================
local sWorld, sRunning, sPaused, sGen
local sScale, sOriginX, sOriginY
local sAccum, sLast, sSub
local sBody -- ctrlRef -> body handle
local sCtrl -- body handle -> ctrlRef
local sShapeH -- ctrlRef -> shape handle (for material edits)
local sRender -- ctrlRef -> "poly" | "ball" | "image" | "loc"
local sVerts -- ctrlRef -> local polygon verts (metres) for "poly"
local sRad -- ctrlRef -> radius (metres) for "ball"
local sImgAngle -- ctrlRef -> last applied angle (deg) for "image"
local sStatic -- ctrlRef -> true for bodies that never move
local sSpawned -- ctrlRef -> true if the kit created the control
local sSensor -- ctrlRef -> true for sensor (non-solid trigger) bodies
local sLayers -- collision-layer name -> bit value
local sNextBit -- next free collision-layer bit
local sDragJoint, sDragAnchor, sDragging
local sContactObj -- object that receives b2kContact messages
local sFrameObj -- object that receives an on b2kFrame message each frame
local sRayX, sRayY -- last b2kRayHit point (screen pixels)
local sRayNX, sRayNY -- last b2kRayHit surface normal (screen-oriented)
local sRayDist -- last b2kRayHit distance from the ray start (pixels)
local sNeedFullSync -- true when a script-side transform/type edit bypassed move events
local sEvtCN, sEvtCA, sEvtCB -- begin-contacts buffered THIS FRAME (body handles)
local sEvtEN, sEvtEA, sEvtEB -- end-contacts buffered this frame
local sEvtSN, sEvtSS, sEvtSV -- sensor enters this frame (sensor, visitor)
local sEvtXN, sEvtXS, sEvtXV -- sensor exits this frame
local sKillY -- kill floor: screen y below which dynamic bodies are removed
local sDrawKey -- ctrlRef -> last rendered pixel/angle key; avoids redundant redraws
local sInputOn -- true while the per-frame keyboard sample is armed
local sInjectOn -- true = the sample comes from sInjectKeys, not the keyboard
local sInjectKeys -- injected keycode list (tests, replays, cutscene ghosts)
local sKeysNow -- comma-wrapped keycode set held this frame (",65361,32,")
local sKeysPrev -- last frame's set; pressed/released edges are the diff
local sKeyActions -- action name -> bound key list ("jump" -> "space,up,w")
local sAxisNeg -- axis name -> negative-direction key list
local sAxisPos -- axis name -> positive-direction key list
local sKeyActionsC -- action name -> RESOLVED keycode list (bind-time cache)
local sAxisNegC -- axis name -> resolved negative keycodes
local sAxisPosC -- axis name -> resolved positive keycodes
local sFrameMS -- real elapsed ms folded into the last frame (b2kFrameMS)
local sSheetSrc -- sheet -> long id of its hidden source image
local sSheetOwned -- sheet -> true when the Kit created the source image
local sSheetRegion -- sheet -> frame key -> "x,y,w,h" region (source px)
local sSheetKeys -- sheet -> CR list of frame keys, definition order
local sSheetIcon -- sheet -> frame key -> sliced frame image id (lazy)
local sSheetFlip -- sheet -> frame key -> mirrored image id (lazy)
local sSheetData -- sheet -> cached source imageData (freed on teardown)
local sSheetAlpha -- sheet -> cached source alphaData
local sSheetScale -- sheet -> display scale factor (default 1; engine-resampled at slice time)
local sAnimList -- "sheet|anim" -> CR list of frame keys
local sAnimFPS -- "sheet|anim" -> frames per second
local sAnimLoop -- "sheet|anim" -> true/false
local sSprRefs -- CR list of every live sprite control
local sSprLive -- ...the subset the tick must service (bound/playing)
local sSprLiveDirty -- true -> rebuild sSprLive from sSprRefs next tick
local sSprSheet, sSprKind, sSprAnim, sSprStep, sSprNextMS
local sSprFPS, sSprFPSOver, sSprFlip, sSprMsg, sSprFrameKey, sSprIconNow
local sSprBind, sSprBindDX, sSprBindDY, sSprLastLoc
local sCamGroup -- viewport group long id while the camera is on
local sCamFollow -- control the camera tracks (empty = manual)
local sCamLerp -- follow smoothing 0..1 (1 = snap)
local sCamDZW, sCamDZH -- deadzone box in px (0 = always centre)
local sCamB1X, sCamB1Y, sCamB2X, sCamB2Y -- world bounds (sCamB1X empty = none)
local sCamX, sCamY -- current view centre (world px)
local sCamShakeAmp, sCamShakeEnd
local sCamLastH, sCamLastV -- last written scroll (skip redundant sets)
local sCamNote -- empty = healthy; else why the camera is degraded
local sCamLocVisual -- true when this engine reports grouped locs SCROLL-ADJUSTED
local sCamCurH, sCamCurV -- the scroll actually applied this frame (cached)
local sInCam -- ctrlRef -> true when the Kit placed it inside the viewport
local sPlayRef -- the player's control (empty = no player controller)
local sPlayArt -- the sprite whose anims the controller drives
local sPlayTune -- tuning key -> value (b2kPlayerSet; defaults fill gaps)
local sPlayAnims -- state -> animation name (idle/run/jump/fall/land)
local sPlayState -- idle | run | jump | fall | land (land lasts one tick)
local sPlayFacing -- 1 right / -1 left (last horizontal intent)
local sPlayGrounded -- this frame's ray-probe verdict
local sPlayGroundMS -- when last grounded (the coyote window)
local sPlayPressMS -- when jump was last pressed (the buffer window)
local sPlayJumping -- a controller jump is still ascending (cut-eligible)
local sPlayControl -- false = observe only: no velocity or anim writes
local sPlayOwnBody -- true when the controller created the body
local sPlayHalfW, sPlayHalfH -- capsule half-extents in px (probe geometry)
local sPlayAnimNow -- last animation written (redundancy suppression)
local sPlayFlipNow -- last flip written
local sPlayHoldMS -- suppress anim switches until then (land flourish)
-- hot-path caches (b2kPlayerTuneCache): the tick runs every frame, so
-- the nine knobs resolve at SET time, not per use
local sPlayMoveSpd, sPlayAccelG, sPlayAccelA, sPlayJumpSpd, sPlayJumpCut
local sPlayCoyote, sPlayBuffer, sPlayMaxFall, sPlayCosSlope
local sPlayProbeOffs -- the 3 probe x-offsets, precomputed at attach
local sPlayReach -- probe ray length (halfH + 4), precomputed
local sPlayAir -- consecutive airborne ticks (landing hysteresis)
local sPlayNormX -- surface normal X at the grounded probe hit (slope test)
local sPlayClock -- the player's SIM-TIME clock: summed frame ms.
-- Wall-clock would shrink the coyote/buffer windows
-- on slow machines (90ms = fewer frames); sim time
-- keeps them frame-coherent everywhere and makes
-- hand-stepped tests deterministic.
local sSndClip -- sound name -> audioClip short name ("b2ksnd_...")
local sSndMute -- true = swallow play calls (a user preference; survives teardown)
local sSndDead -- true after a play failure: degrade to silence, never errors
local sSndNote -- empty = healthy; else why audio is degraded
constant kPI = 3.14159265358979
-- =====================================================================
-- World / lifecycle
-- =====================================================================
command b2kSetup pGravityX, pGravityY
b2kTeardown
if pGravityX is empty then put 0 into pGravityX
if pGravityY is empty then put -10 into pGravityY
put b2NewWorld(pGravityX, pGravityY, true, true) into sWorld
put b2NewStaticBody(sWorld, 0, 0) into sDragAnchor
put 40 into sScale
put (the width of this card) div 2 into sOriginX
put (the height of this card) - 40 into sOriginY
put 4 into sSub
b2kResetTables
put empty into sLayers
put 1 into sNextBit
put 0 into sDragJoint
put false into sDragging
put false into sRunning
put false into sPaused
put false into sNeedFullSync
put empty into sKillY
b2kEventsReset
end b2kSetup
command b2kResetTables
put empty into sBody
put empty into sCtrl
put empty into sShapeH
put empty into sRender
put empty into sVerts
put empty into sRad
put empty into sImgAngle
put empty into sDrawKey
put empty into sStatic
put false into sNeedFullSync
put empty into sSpawned
put empty into sSensor
put empty into sInCam
end b2kResetTables
command b2kTeardown
put false into sRunning
put the milliseconds & random(9999) into sGen
if sWorld is not empty then b2DestroyWorld sWorld
put empty into sWorld
b2kPlayerForget true -- full: a teardown wipes the tuning too
b2kSheetsWipe -- sprites first: their stored long ids include the group
-- sounds deliberately SURVIVE teardown: clips are tiny (KBs) and
-- deterministic, and re-synthesis cost a fifth of a second on every
-- reset. b2kSoundsWipe purges them when you really want them gone.
b2kCamOff -- ...then dissolve the viewport (survivors go to the card)
b2kResetTables
put 0 into sDragJoint
put false into sDragging
end b2kTeardown
-- One-liner: world with gravity, walls around the card, loop running.
command b2kQuickStart pGravityY
b2kSetup 0, pGravityY
b2kAddWalls
b2kStart
end b2kQuickStart
command b2kStart
if sWorld is empty then b2kSetup
put true into sRunning
put false into sPaused
put 0 into sAccum
put the milliseconds into sLast
put the milliseconds & "_" & random(1000000) into sGen
send ("b2kStep " & sGen) to me in 16 milliseconds
end b2kStart
command b2kStop
put false into sRunning
put the milliseconds & random(9999) into sGen
end b2kStop
command b2kPause
put true into sPaused
end b2kPause
command b2kResume
put false into sPaused
put the milliseconds into sLast
end b2kResume
function b2kIsRunning
return (sRunning is true) and (sPaused is not true)
end b2kIsRunning
-- Static, invisible walls along the four card edges.
command b2kAddWalls
local tL, tR, tT, tB
put b2kToWorldX(0) into tL
put b2kToWorldX(the width of this card) into tR
put b2kToWorldY(0) into tT
put b2kToWorldY(the height of this card) into tB
b2kEdge tL, tB, tR, tB
b2kEdge tL, tT, tR, tT
b2kEdge tL, tB, tL, tT
b2kEdge tR, tB, tR, tT
end b2kAddWalls
command b2kEdge pWx1, pWy1, pWx2, pWy2
local tBody
if sWorld is empty then exit b2kEdge -- no world yet: a wall is a quiet no-op
put b2NewStaticBody(sWorld, 0, 0) into tBody
get b2AddSegment(tBody, pWx1, pWy1, pWx2, pWy2, 0.6, 0.1)
end b2kEdge
-- A wide invisible ground line at screen height pScreenY (default: near bottom).
command b2kAddGround pScreenY
local tBody, tWy
if sWorld is empty then exit b2kAddGround
if pScreenY is empty then put (the height of this card) - 40 into pScreenY
put b2kToWorldY(pScreenY) into tWy
put b2NewStaticBody(sWorld, 0, tWy) into tBody
get b2AddSegment(tBody, -500, 0, 500, 0, 0.7, 0.1)
end b2kAddGround
-- A static collision segment between two SCREEN points — custom walls, ramps,
-- ledges and floors. Like the card-edge walls it is invisible (draw your own
-- graphic to match) and lives for the world's lifetime; it isn't tracked as a
-- body, so b2kClear leaves it in place.
-- Destroy any moving Kit body whose centre falls below this SCREEN y
-- (empty = off). Crates shoved into pits, enemies knocked off the level:
-- gone instead of falling forever. The PLAYER's body is exempt (the game
-- owns its respawn). Each removal first sends "b2kFell <ctrl>" to the
-- frame target so the game can clean up companions (bound sprites, table
-- slots) -- do not delete the control inside that handler; the Kit is
-- mid-removal (Kit-spawned controls are deleted for you).
command b2kKillFloor pScreenY
put pScreenY into sKillY
end b2kKillFloor
command b2kWall pX1, pY1, pX2, pY2
b2kEdge b2kToWorldX(pX1), b2kToWorldY(pY1), b2kToWorldX(pX2), b2kToWorldY(pY2)
end b2kWall
-- Remove every dynamic body (b2kRemove deletes kit-spawned controls; attached
-- controls are just detached). Sprites are Kit-created controls, so they go
-- too; loaded sheets stay (they are assets, not world state).
command b2kClear
local tRef, tList, d
b2kSpritesClear
b2kPlayerForget false -- the player is world state; its tuning is config
put empty into tList
repeat for each key tRef in sBody
if sStatic[tRef] is not true then put tRef & cr after tList
end repeat
repeat for each line d in tList
if d is not empty then b2kRemove d
end repeat
end b2kClear
-- =====================================================================
-- Configuration
-- =====================================================================
function b2kNumberOr pValue, pDefault
if pValue is empty then return pDefault
if pValue is not a number then return pDefault
return pValue
end b2kNumberOr
function b2kClamp pValue, pLo, pHi
local tValue
put b2kNumberOr(pValue, pLo) into tValue
if tValue < pLo then put pLo into tValue
if tValue > pHi then put pHi into tValue
return tValue
end b2kClamp
command b2kSetScale pPixelsPerMetre
put b2kClamp(pPixelsPerMetre, 1, 10000) into sScale
end b2kSetScale
command b2kSetOrigin pScreenX, pScreenY
put b2kNumberOr(pScreenX, sOriginX) into sOriginX
put b2kNumberOr(pScreenY, sOriginY) into sOriginY
end b2kSetOrigin
command b2kSetGravity pGx, pGy
put b2kNumberOr(pGx, 0) into pGx
put b2kNumberOr(pGy, -10) into pGy
if sWorld is not empty then b2SetGravity sWorld, pGx, pGy
end b2kSetGravity
command b2kSetSubsteps pN
put round(b2kClamp(pN, 1, 64)) into sSub
end b2kSetSubsteps
-- World toggles: island sleeping (saves CPU) and continuous collision (CCD,
-- stops fast bodies tunnelling through thin walls).
command b2kEnableSleeping pFlag
if sWorld is not empty then b2EnableSleeping sWorld, (pFlag is true)
end b2kEnableSleeping
command b2kEnableContinuous pFlag
if sWorld is not empty then b2EnableContinuous sWorld, (pFlag is true)
end b2kEnableContinuous
command b2kContactTarget pObject
put pObject into sContactObj
end b2kContactTarget
-- Object that receives an `on b2kFrame` message once per simulated frame (after
-- bodies are synced). Use it for per-frame logic: motors, custom drawing, input.
command b2kFrameTarget pObject
put pObject into sFrameObj
end b2kFrameTarget
function b2kWorld
return sWorld
end b2kWorld
-- Sanity check from kit-only code: returns the native shim's ABI version (4),
-- i.e. that the box2dxt extension and its native library are loaded and in sync.
function b2kVersion
return b2Version()
end b2kVersion
-- =====================================================================
-- Coordinate conversion (sim Y up; screen Y down)
-- =====================================================================
function b2kToScreenX pWx
return sOriginX + pWx * sScale
end b2kToScreenX
function b2kToScreenY pWy
return sOriginY - pWy * sScale
end b2kToScreenY
function b2kToWorldX pSx
if sScale is empty or sScale = 0 then return 0 -- before b2kSetup: stay safe
return (pSx - sOriginX) / sScale
end b2kToWorldX
function b2kToWorldY pSy
if sScale is empty or sScale = 0 then return 0
return (sOriginY - pSy) / sScale
end b2kToWorldY
-- =====================================================================
-- Attaching existing controls (pDynamic defaults to true)
-- =====================================================================
command b2kAddBox pControl, pDynamic
local tRef, tType, tFixed, tBody, tHw, tHh, tWx, tWy, tGraphic, tImage
if sWorld is empty then return 0 -- attach needs a world (b2kSetup first)
put the long id of pControl into tRef
put (pDynamic is not false) into pDynamic
if pDynamic then put 2 into tType
else put 0 into tType
put ((the width of pControl) / 2) / sScale into tHw
put ((the height of pControl) / 2) / sScale into tHh
if tHw <= 0 or tHh <= 0 then return 0
put b2kToWorldX(item 1 of the loc of pControl) into tWx
put b2kToWorldY(item 2 of the loc of pControl) into tWy
put (word 1 of tRef is "graphic") into tGraphic
put (word 1 of tRef is "image") into tImage
-- Graphics rotate as polygons; dynamic images rotate via `the angle`. Other
-- control types (button, field, ...) can't be shown rotated, so we lock
-- their rotation to keep the simulation consistent with the upright render.
if tGraphic or (tImage and pDynamic) then put false into tFixed
else put true into tFixed
put b2NewBody(sWorld, tType, tWx, tWy, 0, false, tFixed) into tBody
b2ShapeDefEnableSensorEvents true -- so sensors can detect this shape
put b2AddBox(tBody, tHw, tHh, 1.0, 0.4, 0.2) into sShapeH[tRef]
b2kRegister tRef, tBody, (not pDynamic)
if tGraphic and pDynamic then
set the style of pControl to "polygon"
put "poly" into sRender[tRef]
put ("-" & tHw) & "," & ("-" & tHh) & cr & tHw & "," & ("-" & tHh) & cr \
& tHw & "," & tHh & cr & ("-" & tHw) & "," & tHh into sVerts[tRef]
else if tImage and pDynamic then
put "image" into sRender[tRef]
else
put "loc" into sRender[tRef]
end if
-- Draw the new body NOW. The style switch above left a "poly" graphic with
-- no points (invisible), and the move-event sync can't help: a world that
-- isn't stepping emits no move events, so a part spawned while the loop is
-- stopped would stay invisible until the first Run. Attach = visible.
b2kDrawBody tRef, tWx, tWy, 0
return tBody
end b2kAddBox
command b2kAddBall pControl, pDynamic
local tRef, tType, tBody, tRad, tWx, tWy
if sWorld is empty then return 0
put the long id of pControl into tRef
put (pDynamic is not false) into pDynamic
if pDynamic then put 2 into tType
else put 0 into tType
put ((the width of pControl) / 2) / sScale into tRad
if tRad <= 0 then return 0
put b2kToWorldX(item 1 of the loc of pControl) into tWx
put b2kToWorldY(item 2 of the loc of pControl) into tWy
put b2NewBody(sWorld, tType, tWx, tWy, 0, false, false) into tBody
b2ShapeDefEnableSensorEvents true -- so sensors can detect this shape
put b2AddCircle(tBody, 0, 0, tRad, 1.0, 0.4, 0.4) into sShapeH[tRef]
b2kRegister tRef, tBody, (not pDynamic)
if word 1 of tRef is "graphic" then put "ball" into sRender[tRef]
else if word 1 of tRef is "image" then put "image" into sRender[tRef]
else put "loc" into sRender[tRef]
put tRad into sRad[tRef]
b2kDrawBody tRef, tWx, tWy, 0 -- visible at attach, even with the loop stopped
return tBody
end b2kAddBall
-- Capsule ("pill"): the long side of the control's rect is the capsule axis,
-- the short side its diameter. Graphics render as a rounded outline (and rotate);
-- images rotate via `the angle`; other controls follow position upright.
command b2kAddCapsule pControl, pDynamic
local tRef, tType, tFixed, tBody, tWx, tWy, tWm, tHm, tR, tL, tHoriz, tGraphic, tImage
if sWorld is empty then return 0
put the long id of pControl into tRef
put (pDynamic is not false) into pDynamic
if pDynamic then put 2 into tType
else put 0 into tType
put (the width of pControl) / sScale into tWm
put (the height of pControl) / sScale into tHm
if tWm <= 0 or tHm <= 0 then return 0
put (tWm >= tHm) into tHoriz
if tHoriz then
put tHm / 2 into tR
put max(0, tWm / 2 - tR) into tL
else
put tWm / 2 into tR
put max(0, tHm / 2 - tR) into tL
end if
put b2kToWorldX(item 1 of the loc of pControl) into tWx
put b2kToWorldY(item 2 of the loc of pControl) into tWy
put (word 1 of tRef is "graphic") into tGraphic
put (word 1 of tRef is "image") into tImage
if tGraphic or (tImage and pDynamic) then put false into tFixed
else put true into tFixed
put b2NewBody(sWorld, tType, tWx, tWy, 0, false, tFixed) into tBody
b2ShapeDefEnableSensorEvents true -- so sensors can detect this shape
if tHoriz then
put b2AddCapsule(tBody, - tL, 0, tL, 0, tR, 1.0, 0.4, 0.2) into sShapeH[tRef]
else
put b2AddCapsule(tBody, 0, - tL, 0, tL, tR, 1.0, 0.4, 0.2) into sShapeH[tRef]
end if
b2kRegister tRef, tBody, (not pDynamic)
if tGraphic and pDynamic then
set the style of pControl to "polygon"
put "poly" into sRender[tRef]
put b2kCapsuleVerts(tL, tR, tHoriz) into sVerts[tRef]
else if tImage and pDynamic then
put "image" into sRender[tRef]
else
put "loc" into sRender[tRef]
end if
-- Same as b2kAddBox: the freshly point-less "poly" graphic must be drawn
-- here, because a stopped world never emits the move event that would.
b2kDrawBody tRef, tWx, tWy, 0
return tBody
end b2kAddCapsule
-- Build a body from a polygon graphic's own points (<= 8 vertices, convex).
command b2kAddPolygon pControl, pDynamic
local tRef, tType, tBody, tCx, tCy, tWx, tWy, tPoints, p, tLocal, lx, ly, tCount
if sWorld is empty then return 0
put the long id of pControl into tRef
put (pDynamic is not false) into pDynamic
if pDynamic then put 2 into tType
else put 0 into tType
put item 1 of the loc of pControl into tCx
put item 2 of the loc of pControl into tCy
put b2kToWorldX(tCx) into tWx
put b2kToWorldY(tCy) into tWy
put b2NewBody(sWorld, tType, tWx, tWy, 0, false, false) into tBody
b2PolyBegin
put empty into tLocal
put 0 into tCount
repeat for each line p in the points of pControl
if p is empty then next repeat
if tCount >= 8 then next repeat
put (item 1 of p - tCx) / sScale into lx
put - ((item 2 of p - tCy) / sScale) into ly
b2PolyAddPoint lx, ly
put lx & "," & ly & cr after tLocal
add 1 to tCount
end repeat
if tCount < 3 then
b2DestroyBody tBody
return 0
end if
b2ShapeDefEnableSensorEvents true -- so sensors can detect this shape
put b2AddPolygon(tBody, 1.0, 0.4, 0.2) into sShapeH[tRef]
b2kRegister tRef, tBody, (not pDynamic)
put "poly" into sRender[tRef]
put tLocal into sVerts[tRef]
b2kDrawBody tRef, tWx, tWy, 0 -- visible at attach, even with the loop stopped
return tBody
end b2kAddPolygon
command b2kAddStatic pControl
b2kAddBox pControl, false
end b2kAddStatic
command b2kRegister pRef, pBody, pIsStatic
put pBody into sBody[pRef]
put pRef into sCtrl[pBody]
put (pIsStatic is true) into sStatic[pRef]
end b2kRegister
-- =====================================================================
-- Spawning new controls (create + attach in one call). Returns control ref.
-- =====================================================================
command b2kSpawnBox pScreenX, pScreenY, pW, pH, pColor
local tName, tRef
if sWorld is empty then return empty -- spawn needs a world (b2kSetup first)
if pW is empty then put 40 into pW
put b2kClamp(pW, 1, 10000) into pW
if pH is empty then put pW into pH
put b2kClamp(pH, 1, 10000) into pH
put b2kNumberOr(pScreenX, sOriginX) into pScreenX
put b2kNumberOr(pScreenY, sOriginY) into pScreenY
put "b2kspawn_" & the milliseconds & "_" & random(1000000) into tName
if sCamGroup is empty then
create graphic (tName)
else
create graphic (tName) in group "b2kcam_view"
end if
put the long id of graphic (tName) into tRef
if sCamGroup is not empty then put true into sInCam[tRef]
set the style of tRef to "rectangle"
set the filled of tRef to true
set the foregroundColor of tRef to "20,20,24"
if pColor is not empty then
try
set the backgroundColor of tRef to pColor
catch tErr
-- an unknown colour name (CSS names like "teal" are not LC/X11
-- names) must not abort the spawn and orphan the control
end try
end if
set the width of tRef to pW
set the height of tRef to pH
set the loc of tRef to round(pScreenX - b2kCamShiftX(tRef)) & "," & round(pScreenY - b2kCamShiftY(tRef))
b2kAddBox tRef
put true into sSpawned[tRef]
return tRef
end b2kSpawnBox
command b2kSpawnBall pScreenX, pScreenY, pDiameter, pColor
local tName, tRef
if sWorld is empty then return empty
if pDiameter is empty then put 40 into pDiameter
put b2kClamp(pDiameter, 1, 10000) into pDiameter
put b2kNumberOr(pScreenX, sOriginX) into pScreenX
put b2kNumberOr(pScreenY, sOriginY) into pScreenY
put "b2kspawn_" & the milliseconds & "_" & random(1000000) into tName
if sCamGroup is empty then
create graphic (tName)
else
create graphic (tName) in group "b2kcam_view"
end if
put the long id of graphic (tName) into tRef
if sCamGroup is not empty then put true into sInCam[tRef]
set the style of tRef to "oval"
set the filled of tRef to true
set the foregroundColor of tRef to "20,20,24"
if pColor is not empty then
try
set the backgroundColor of tRef to pColor
catch tErr
-- an unknown colour name (CSS names like "teal" are not LC/X11
-- names) must not abort the spawn and orphan the control
end try
end if
set the width of tRef to pDiameter
set the height of tRef to pDiameter
set the loc of tRef to round(pScreenX - b2kCamShiftX(tRef)) & "," & round(pScreenY - b2kCamShiftY(tRef))
b2kAddBall tRef
put true into sSpawned[tRef]
return tRef
end b2kSpawnBall
-- Create a pill-shaped graphic and attach a capsule body. pLength is along the
-- pill's long axis, pThickness across it; horizontal if pLength >= pThickness.
command b2kSpawnCapsule pScreenX, pScreenY, pLength, pThickness, pColor
local tName, tRef
if sWorld is empty then return empty
if pLength is empty then put 80 into pLength
if pThickness is empty then put 40 into pThickness
put b2kClamp(pLength, 1, 10000) into pLength
put b2kClamp(pThickness, 1, 10000) into pThickness
put b2kNumberOr(pScreenX, sOriginX) into pScreenX
put b2kNumberOr(pScreenY, sOriginY) into pScreenY
put "b2kspawn_" & the milliseconds & "_" & random(1000000) into tName
if sCamGroup is empty then
create graphic (tName)
else
create graphic (tName) in group "b2kcam_view"
end if
put the long id of graphic (tName) into tRef
if sCamGroup is not empty then put true into sInCam[tRef]
set the style of tRef to "polygon"
set the filled of tRef to true
set the foregroundColor of tRef to "20,20,24"
if pColor is not empty then
try
set the backgroundColor of tRef to pColor
catch tErr
-- an unknown colour name (CSS names like "teal" are not LC/X11
-- names) must not abort the spawn and orphan the control
end try
end if
if pLength >= pThickness then
set the width of tRef to pLength
set the height of tRef to pThickness
else
set the width of tRef to pThickness
set the height of tRef to pLength
end if
set the loc of tRef to round(pScreenX - b2kCamShiftX(tRef)) & "," & round(pScreenY - b2kCamShiftY(tRef))
b2kAddCapsule tRef
put true into sSpawned[tRef]
return tRef
end b2kSpawnCapsule
-- =====================================================================
-- Materials & body properties
-- =====================================================================
command b2kSetBounce pControl, pRestitution
local s
put sShapeH[the long id of pControl] into s
put b2kClamp(pRestitution, 0, 2) into pRestitution
if s is not empty then b2SetShapeRestitution s, pRestitution
end b2kSetBounce
command b2kSetFriction pControl, pFriction
local s
put sShapeH[the long id of pControl] into s
put b2kClamp(pFriction, 0, 10) into pFriction
if s is not empty then b2SetShapeFriction s, pFriction
end b2kSetFriction
command b2kSetDensity pControl, pDensity
local s
put sShapeH[the long id of pControl] into s
put b2kClamp(pDensity, 0, 10000) into pDensity
if s is not empty then b2SetShapeDensity s, pDensity
end b2kSetDensity
-- Re-fit a body's collision shape to the control's CURRENT size, in place on the
-- SAME body so any joints stay valid. pShape is "box" | "ball" | "capsule" | "poly".
-- Resize the control's graphic first, then re-apply materials afterwards.
command b2kReshape pControl, pShape
local tRef, tBody, tOld, tHw, tHh, tWm, tHm, tR, tL, tHoriz, tCx, tCy, p, lx, ly, tCount, tNewVerts
local tWx, tWy, tWa
put the long id of pControl into tRef
if sBody[tRef] is empty then exit b2kReshape
put sBody[tRef] into tBody
-- Build the new shape first and drop the old one only afterwards, so the body
-- is never momentarily shapeless and a rejected shape leaves the original
-- intact (any joints on the body stay valid throughout).
put sShapeH[tRef] into tOld
put empty into sShapeH[tRef]
b2ShapeDefEnableSensorEvents true -- keep the reshaped body detectable by sensors
if sSensor[tRef] is true then b2ShapeDefSensor true -- a sensor stays a sensor after reshaping
switch pShape
case "ball"
put ((the width of pControl) / 2) / sScale into tR
if tR <= 0 then
put tOld into sShapeH[tRef]
b2ShapeDefReset -- drop the pending one-shot flags set above
exit b2kReshape
end if
put b2AddCircle(tBody, 0, 0, tR, 1.0, 0.4, 0.4) into sShapeH[tRef]
put tR into sRad[tRef]
break
case "capsule"
put (the width of pControl) / sScale into tWm
put (the height of pControl) / sScale into tHm
if tWm <= 0 or tHm <= 0 then
put tOld into sShapeH[tRef]
b2ShapeDefReset -- drop the pending one-shot flags set above
exit b2kReshape
end if
put (tWm >= tHm) into tHoriz
if tHoriz then
put tHm / 2 into tR
put max(0, tWm / 2 - tR) into tL
put b2AddCapsule(tBody, - tL, 0, tL, 0, tR, 1.0, 0.4, 0.2) into sShapeH[tRef]
else
put tWm / 2 into tR
put max(0, tHm / 2 - tR) into tL
put b2AddCapsule(tBody, 0, - tL, 0, tL, tR, 1.0, 0.4, 0.2) into sShapeH[tRef]
end if
put b2kCapsuleVerts(tL, tR, tHoriz) into sVerts[tRef]
break
case "poly"
put item 1 of the loc of pControl into tCx
put item 2 of the loc of pControl into tCy
b2PolyBegin
put empty into tNewVerts
put 0 into tCount
repeat for each line p in the points of pControl
if p is empty then next repeat
if tCount >= 8 then next repeat
put (item 1 of p - tCx) / sScale into lx
put - ((item 2 of p - tCy) / sScale) into ly
b2PolyAddPoint lx, ly
put lx & "," & ly & cr after tNewVerts
add 1 to tCount
end repeat
if tCount < 3 then -- degenerate outline: keep the existing shape
put tOld into sShapeH[tRef]
b2ShapeDefReset -- drop the pending one-shot flags set above
exit b2kReshape
end if
put b2AddPolygon(tBody, 1.0, 0.4, 0.2) into sShapeH[tRef]
put tNewVerts into sVerts[tRef]
break
default
put ((the width of pControl) / 2) / sScale into tHw
put ((the height of pControl) / 2) / sScale into tHh
if tHw <= 0 or tHh <= 0 then
put tOld into sShapeH[tRef]
b2ShapeDefReset -- drop the pending one-shot flags set above
exit b2kReshape
end if
put b2AddBox(tBody, tHw, tHh, 1.0, 0.4, 0.2) into sShapeH[tRef]
if sRender[tRef] is "poly" then
put ("-" & tHw) & "," & ("-" & tHh) & cr & tHw & "," & ("-" & tHh) & cr \
& tHw & "," & tHh & cr & ("-" & tHw) & "," & tHh into sVerts[tRef]
end if
end switch
if tOld is not empty then b2DestroyShape tOld -- now safe to release the old shape
-- The size/outline changed but the body need not have moved, so the move-event
-- sync would skip it (an at-rest body produces no move event). Bust the
-- pose-keyed draw cache and redraw at the body's current pose right now so the
-- new shape is visible immediately, awake or asleep.
delete variable sDrawKey[tRef]
put b2BodyX(tBody) into tWx
put b2BodyY(tBody) into tWy
if sRender[tRef] is "poly" or sRender[tRef] is "image" then put b2BodyAngle(tBody) into tWa
else put 0 into tWa
lock screen
b2kDrawBody tRef, tWx, tWy, tWa
unlock screen
end b2kReshape
-- Every body-targeting wrapper resolves the control's body first and quietly
-- no-ops when there is none (unregistered, removed, or pre-setup): the kit's
-- contract is that a missing body is never a script error.
command b2kSetBullet pControl, pFlag
local b
put sBody[the long id of pControl] into b
if b is empty then exit b2kSetBullet
b2SetBullet b, (pFlag is true)
end b2kSetBullet
command b2kSetFixedRotation pControl, pFlag
local b
put sBody[the long id of pControl] into b
if b is empty then exit b2kSetFixedRotation
b2SetFixedRotation b, (pFlag is true)
end b2kSetFixedRotation
command b2kSetGravityScale pControl, pScale
local b
put sBody[the long id of pControl] into b
if b is empty then exit b2kSetGravityScale
b2SetGravityScale b, pScale
end b2kSetGravityScale
command b2kSetDamping pControl, pLinear, pAngular
local b
put sBody[the long id of pControl] into b
if b is empty then exit b2kSetDamping
if pLinear is not empty then b2SetLinearDamping b, pLinear
if pAngular is not empty then b2SetAngularDamping b, pAngular
end b2kSetDamping
command b2kWake pControl
local b
put sBody[the long id of pControl] into b
if b is empty then exit b2kWake
b2SetAwake b, true
end b2kWake
-- Send a body to sleep (it stops simulating until something wakes it).
command b2kSleep pControl
local b
put sBody[the long id of pControl] into b
if b is empty then exit b2kSleep
b2SetAwake b, false
end b2kSleep
-- Allow or forbid THIS body ever falling asleep (vs b2kEnableSleeping,
-- which switches the whole world). The player controller forbids it: a
-- character must respond to input even after standing still for minutes.
command b2kSetSleepEnabled pControl, pFlag
local b
put sBody[the long id of pControl] into b
if b is empty then exit b2kSetSleepEnabled
b2EnableSleep b, (pFlag is true)
if pFlag is not true then b2SetAwake b, true
end b2kSetSleepEnabled
-- Below this speed (pixels/sec) a body may fall asleep. Lower = stays awake
-- longer; higher = sleeps sooner (saves CPU).
command b2kSetSleepThreshold pControl, pPxPerSec
local b
put sBody[the long id of pControl] into b
if b is empty then exit b2kSetSleepThreshold
b2SetSleepThreshold b, pPxPerSec / sScale
end b2kSetSleepThreshold
-- =====================================================================
-- Acting on bodies (velocities/kicks are in PIXELS/sec, screen-oriented)
-- =====================================================================
command b2kSetVelocity pControl, pVxPx, pVyPx
local b
put sBody[the long id of pControl] into b
if b is empty then exit b2kSetVelocity
b2SetVelocity b, pVxPx / sScale, - pVyPx / sScale
-- setting a velocity means "move": wake the body. Raw SetVelocity
-- does NOT wake (why b2kPush always called SetAwake) - a SLEEPING
-- kinematic (a parked gate) given one cached write stayed frozen,
-- which read as "the pressure plate is flaky".
b2SetAwake b, true
end b2kSetVelocity
command b2kPush pControl, pDvxPx, pDvyPx
local b
put sBody[the long id of pControl] into b
if b is empty then exit b2kPush
b2SetVelocity b, b2BodyVX(b) + pDvxPx / sScale, b2BodyVY(b) - pDvyPx / sScale
b2SetAwake b, true
end b2kPush
command b2kSpin pControl, pDegPerSec
local b
put sBody[the long id of pControl] into b
if b is empty then exit b2kSpin
b2SetAngularVelocity b, pDegPerSec * kPI / 180
end b2kSpin
-- Add to the current spin (vs b2kSpin which sets it absolutely).
command b2kSpinBy pControl, pDegPerSec
local b
put sBody[the long id of pControl] into b
if b is empty then exit b2kSpinBy
b2SetAngularVelocity b, b2BodyAngularVelocity(b) + pDegPerSec * kPI / 180
b2SetAwake b, true
end b2kSpinBy
-- Continuous, screen-oriented force (thrusters, wind). Apply each frame for a
-- sustained effect; b2kPush is the one-shot impulse equivalent.
command b2kForce pControl, pFxPx, pFyPx
local b
put sBody[the long id of pControl] into b
if b is empty then exit b2kForce
b2ApplyForce b, pFxPx / sScale, - pFyPx / sScale, true
end b2kForce
-- A one-shot impulse (a sharp kick), screen-oriented. Mass-aware: heavier bodies
-- move less. b2kPush changes velocity directly; this is the impulse equivalent.
command b2kImpulse pControl, pIxPx, pIyPx
local b
put sBody[the long id of pControl] into b
if b is empty then exit b2kImpulse
b2ApplyImpulse b, pIxPx / sScale, - pIyPx / sScale, true
end b2kImpulse
-- A continuous turning force (same units as motor torque). Positive turns one way,
-- negative the other. Apply each frame for a sustained twist; pairs with b2kForce.
command b2kTorque pControl, pTorque
local b
put sBody[the long id of pControl] into b
if b is empty then exit b2kTorque
b2ApplyTorque b, pTorque, true
end b2kTorque
-- A one-shot twist (a sharp angular kick), mass-aware: bodies with more
-- rotational inertia turn less. b2kSpinBy sets the spin directly; this is the
-- impulse equivalent, and the angular partner of b2kImpulse.
command b2kAngularImpulse pControl, pImpulse
local b
put sBody[the long id of pControl] into b
if b is empty then exit b2kAngularImpulse
b2ApplyAngularImpulse b, pImpulse, true