├── addons
├── debug_draw_3d
│ ├── libs
│ │ ├── .gdignore
│ │ ├── libdd3d.linux.editor.x86_64.so
│ │ ├── libdd3d.windows.editor.x86_64.dll
│ │ ├── libdd3d.web.template_debug.wasm32.wasm
│ │ ├── libdd3d.android.template_debug.arm32.so
│ │ ├── libdd3d.android.template_debug.arm64.so
│ │ ├── libdd3d.android.template_debug.x86_32.so
│ │ ├── libdd3d.android.template_debug.x86_64.so
│ │ ├── libdd3d.android.template_release.arm32.so
│ │ ├── libdd3d.android.template_release.arm64.so
│ │ ├── libdd3d.linux.template_release.x86_64.so
│ │ ├── libdd3d.web.template_release.wasm32.wasm
│ │ ├── libdd3d.android.template_release.x86_32.so
│ │ ├── libdd3d.android.template_release.x86_64.so
│ │ ├── libdd3d.ios.template_debug.universal.dylib
│ │ ├── libdd3d.windows.template_release.x86_64.dll
│ │ ├── libdd3d.ios.template_release.universal.dylib
│ │ ├── libdd3d.web.template_debug.wasm32.threads.wasm
│ │ ├── libdd3d.linux.template_release.x86_64.enabled.so
│ │ ├── libdd3d.web.template_release.wasm32.enabled.wasm
│ │ ├── libdd3d.web.template_release.wasm32.threads.wasm
│ │ ├── libdd3d.windows.template_release.x86_64.enabled.dll
│ │ ├── libdd3d.ios.template_release.universal.enabled.dylib
│ │ ├── libdd3d.web.template_release.wasm32.threads.enabled.wasm
│ │ ├── libdd3d.macos.editor.universal.framework
│ │ │ ├── libdd3d.macos.editor.universal.dylib
│ │ │ └── Resources
│ │ │ │ └── Info.plist
│ │ ├── libdd3d.macos.template_release.universal.framework
│ │ │ ├── libdd3d.macos.template_release.universal.dylib
│ │ │ └── Resources
│ │ │ │ └── Info.plist
│ │ └── libdd3d.macos.template_release.universal.enabled.framework
│ │ │ ├── libdd3d.macos.template_release.universal.enabled.dylib
│ │ │ └── Resources
│ │ │ └── Info.plist
│ ├── debug_draw_3d.gdextension.uid
│ └── LICENSE
├── netfox
│ ├── netfox.gd.uid
│ ├── interpolators.gd.uid
│ ├── network-time.gd.uid
│ ├── network-events.gd.uid
│ ├── rewindable-action.gd.uid
│ ├── state-synchronizer.gd.uid
│ ├── tick-interpolator.gd.uid
│ ├── network-performance.gd.uid
│ ├── peer-visibility-filter.gd.uid
│ ├── time
│ │ ├── network-clocks.gd.uid
│ │ ├── network-clock-sample.gd.uid
│ │ ├── network-tickrate-handshake.gd.uid
│ │ ├── network-clock-sample.gd
│ │ ├── network-clocks.gd
│ │ └── network-tickrate-handshake.gd
│ ├── encoder
│ │ ├── diff-history-encoder.gd.uid
│ │ ├── redundant-history-encoder.gd.uid
│ │ ├── snapshot-history-encoder.gd.uid
│ │ ├── snapshot-history-encoder.gd
│ │ └── redundant-history-encoder.gd
│ ├── network-time-synchronizer.gd.uid
│ ├── properties
│ │ ├── property-cache.gd.uid
│ │ ├── property-config.gd.uid
│ │ ├── property-entry.gd.uid
│ │ ├── property-snapshot.gd.uid
│ │ ├── property-history-buffer.gd.uid
│ │ ├── property-cache.gd
│ │ ├── property-history-buffer.gd
│ │ ├── property-config.gd
│ │ ├── property-entry.gd
│ │ └── property-snapshot.gd
│ ├── rollback
│ │ ├── network-rollback.gd.uid
│ │ ├── rollback-freshness-store.gd.uid
│ │ ├── rollback-synchronizer.gd.uid
│ │ ├── composite
│ │ │ ├── rollback-history-recorder.gd.uid
│ │ │ ├── rollback-history-transmitter.gd.uid
│ │ │ └── rollback-history-recorder.gd
│ │ └── rollback-freshness-store.gd
│ ├── serializers
│ │ ├── tickset-serializer.gd.uid
│ │ └── tickset-serializer.gd
│ ├── plugin.cfg
│ ├── icons
│ │ ├── rewindable-action.svg.import
│ │ ├── tick-interpolator.svg.import
│ │ ├── state-synchronizer.svg.import
│ │ ├── rollback-synchronizer.svg.import
│ │ ├── rollback-synchronizer.svg
│ │ ├── state-synchronizer.svg
│ │ ├── tick-interpolator.svg
│ │ └── rewindable-action.svg
│ ├── LICENSE
│ ├── README.md
│ └── interpolators.gd
├── netfox.internals
│ ├── set.gd.uid
│ ├── bimap.gd.uid
│ ├── logger.gd.uid
│ ├── plugin.gd.uid
│ ├── editor-utils.gd.uid
│ ├── ring-buffer.gd.uid
│ ├── history-buffer.gd.uid
│ ├── plugin.cfg
│ ├── README.md
│ ├── ring-buffer.gd
│ ├── plugin.gd
│ ├── LICENSE
│ ├── set.gd
│ ├── bimap.gd
│ ├── history-buffer.gd
│ ├── editor-utils.gd
│ └── logger.gd
├── netfox.noray
│ ├── noray.gd.uid
│ ├── netfox-noray.gd.uid
│ ├── packet-handshake.gd.uid
│ ├── protocol-handler.gd.uid
│ ├── plugin.cfg
│ ├── protocol-handler.gd
│ ├── LICENSE
│ ├── README.md
│ └── netfox-noray.gd
└── netfox.extras
│ ├── base-net-input.gd.uid
│ ├── netfox-extras.gd.uid
│ ├── network-simulator.gd.uid
│ ├── window-tiler.gd.uid
│ ├── weapon
│ ├── network-weapon.gd.uid
│ ├── network-weapon-2d.gd.uid
│ ├── network-weapon-3d.gd.uid
│ ├── network-weapon-proxy.gd.uid
│ ├── network-weapon-hitscan-3d.gd.uid
│ ├── network-weapon-proxy.gd
│ ├── network-weapon-2d.gd
│ ├── network-weapon-3d.gd
│ └── network-weapon-hitscan-3d.gd
│ ├── physics
│ ├── godot_driver_2d.gd.uid
│ ├── godot_driver_3d.gd.uid
│ ├── physics_driver.gd.uid
│ ├── network-rigid-body-2d.gd.uid
│ ├── network-rigid-body-3d.gd.uid
│ ├── physics-driver-toggles.gd.uid
│ ├── rapier_driver_2d.gd.uid.off
│ ├── rapier_driver_3d.gd.uid.off
│ ├── rapier_driver_2d.gd.off
│ ├── rapier_driver_3d.gd.off
│ ├── network-rigid-body-2d.gd
│ ├── network-rigid-body-3d.gd
│ ├── physics-driver-toggles.gd
│ ├── physics_driver.gd
│ ├── godot_driver_3d.gd
│ └── godot_driver_2d.gd
│ ├── state-machine
│ ├── rewindable-state.gd.uid
│ └── rewindable-state-machine.gd.uid
│ ├── rewindable-random-number-generator.gd.uid
│ ├── plugin.cfg
│ ├── base-net-input.gd
│ ├── README.md
│ ├── icons
│ ├── rewindable-state.svg.import
│ ├── network-rigid-body-2d.svg.import
│ ├── network-rigid-body-3d.svg.import
│ ├── rewindable-state-machine.svg.import
│ ├── network-rigid-body-2d.svg
│ ├── network-rigid-body-3d.svg
│ └── rewindable-state-machine.svg
│ ├── LICENSE
│ └── rewindable-random-number-generator.gd
├── scripts
├── ball.gd.uid
├── game.gd.uid
├── goal.gd.uid
├── main.gd.uid
├── camera_3d.gd.uid
├── car_ai.gd.uid
├── confetti.gd.uid
├── input.gd.uid
├── player.gd.uid
├── player_rb.gd.uid
├── ui_info.gd.uid
├── wheel.gd.uid
├── rapier_driver.gd.uid
├── confetti.gd
├── goal.gd
├── input.gd
├── camera_3d.gd
├── main.gd
├── ball.gd
├── ui_info.gd
├── car_ai.gd
├── game.gd
├── player.gd
└── wheel.gd
├── models
├── car.glb
├── car.blend
├── car.blend1
├── field.glb
├── field.blend
├── field.blend1
├── car.glb.import
├── field.glb.import
├── car.blend.import
└── field.blend.import
├── materials
├── ball_orm.png
├── wall_orm.png
├── wall_sss.png
├── grass_orm.png
├── ball_albedo.png
├── ball_normal.png
├── grass_albedo.png
├── grass_normal.png
├── wall_albedo.png
├── wall_normal.png
├── ball_heightmap.png
├── grass_emission.png
├── grass_heightmap.png
├── wall_heightmap.png
├── car_paint.tres
├── car_window.tres
├── wall.tres
├── wall_orm.png.import
├── ball_albedo.png.import
├── wall_albedo.png.import
├── grass_emission.png.import
├── ball_orm.png.import
├── wall_sss.png.import
├── grass_orm.png.import
├── ball_normal.png.import
├── grass_normal.png.import
├── wall_normal.png.import
├── ball_heightmap.png.import
├── wall_heightmap.png.import
├── grass_heightmap.png.import
├── ball.tres
├── grass_albedo.png.import
├── grass.tres
└── wall.ptex
├── scenes
├── car_ai.tscn
├── field.tscn
├── car_model.tscn
├── goal.tscn
├── wheel.tscn
├── ball.tscn
├── player.tscn
├── confetti.tscn
└── player_rb.tscn
├── .gitignore
├── README.md
├── icon.svg
├── icon.svg.import
├── LICENSE
└── project.godot
/addons/debug_draw_3d/libs/.gdignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/scripts/ball.gd.uid:
--------------------------------------------------------------------------------
1 | uid://c0s6il6j1o06a
2 |
--------------------------------------------------------------------------------
/scripts/game.gd.uid:
--------------------------------------------------------------------------------
1 | uid://c7wmmu5kvc8qq
2 |
--------------------------------------------------------------------------------
/scripts/goal.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cau6jor3sbnjm
2 |
--------------------------------------------------------------------------------
/scripts/main.gd.uid:
--------------------------------------------------------------------------------
1 | uid://c6j4p68pfwm0u
2 |
--------------------------------------------------------------------------------
/scripts/camera_3d.gd.uid:
--------------------------------------------------------------------------------
1 | uid://btvo6f7sbrn0q
2 |
--------------------------------------------------------------------------------
/scripts/car_ai.gd.uid:
--------------------------------------------------------------------------------
1 | uid://ddst71w41ihgn
2 |
--------------------------------------------------------------------------------
/scripts/confetti.gd.uid:
--------------------------------------------------------------------------------
1 | uid://bxr4tbk4dq8v0
2 |
--------------------------------------------------------------------------------
/scripts/input.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cbmc5r8tb2ub7
2 |
--------------------------------------------------------------------------------
/scripts/player.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cgwxygf25ypdb
2 |
--------------------------------------------------------------------------------
/scripts/player_rb.gd.uid:
--------------------------------------------------------------------------------
1 | uid://lxgmbbyk3nvi
2 |
--------------------------------------------------------------------------------
/scripts/ui_info.gd.uid:
--------------------------------------------------------------------------------
1 | uid://bt0tpn5p32eym
2 |
--------------------------------------------------------------------------------
/scripts/wheel.gd.uid:
--------------------------------------------------------------------------------
1 | uid://c8tk028y8ex3g
2 |
--------------------------------------------------------------------------------
/addons/netfox/netfox.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cjrtjdfhdtjlm
2 |
--------------------------------------------------------------------------------
/scripts/rapier_driver.gd.uid:
--------------------------------------------------------------------------------
1 | uid://be16aa8reh80u
2 |
--------------------------------------------------------------------------------
/addons/netfox.internals/set.gd.uid:
--------------------------------------------------------------------------------
1 | uid://bjvmp7eed1aj0
2 |
--------------------------------------------------------------------------------
/addons/netfox.noray/noray.gd.uid:
--------------------------------------------------------------------------------
1 | uid://gu4bv4tgms3x
2 |
--------------------------------------------------------------------------------
/addons/netfox/interpolators.gd.uid:
--------------------------------------------------------------------------------
1 | uid://wsx6ajtb42c2
2 |
--------------------------------------------------------------------------------
/addons/netfox/network-time.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cnqdxyjptkfvp
2 |
--------------------------------------------------------------------------------
/addons/netfox.internals/bimap.gd.uid:
--------------------------------------------------------------------------------
1 | uid://b826s26d4ud5j
2 |
--------------------------------------------------------------------------------
/addons/netfox.internals/logger.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dp4rakv0drj1f
2 |
--------------------------------------------------------------------------------
/addons/netfox.internals/plugin.gd.uid:
--------------------------------------------------------------------------------
1 | uid://bvhj7f66i6yl4
2 |
--------------------------------------------------------------------------------
/addons/netfox.noray/netfox-noray.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dj8vvyvvf4cl1
2 |
--------------------------------------------------------------------------------
/addons/netfox/network-events.gd.uid:
--------------------------------------------------------------------------------
1 | uid://bdmobxxptryj0
2 |
--------------------------------------------------------------------------------
/addons/netfox/rewindable-action.gd.uid:
--------------------------------------------------------------------------------
1 | uid://chr4omg2hy3yu
2 |
--------------------------------------------------------------------------------
/addons/netfox/state-synchronizer.gd.uid:
--------------------------------------------------------------------------------
1 | uid://3lbngqcexe0l
2 |
--------------------------------------------------------------------------------
/addons/netfox/tick-interpolator.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dour8fehaaugp
2 |
--------------------------------------------------------------------------------
/addons/netfox.extras/base-net-input.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cpiuf65vuu0oa
2 |
--------------------------------------------------------------------------------
/addons/netfox.extras/netfox-extras.gd.uid:
--------------------------------------------------------------------------------
1 | uid://ee1cb3bj7opr
2 |
--------------------------------------------------------------------------------
/addons/netfox.extras/network-simulator.gd.uid:
--------------------------------------------------------------------------------
1 | uid://1y2ey1e6qjwr
2 |
--------------------------------------------------------------------------------
/addons/netfox.extras/window-tiler.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cmqjajhjgffm1
2 |
--------------------------------------------------------------------------------
/addons/netfox.internals/editor-utils.gd.uid:
--------------------------------------------------------------------------------
1 | uid://0ohd4n5yjilb
2 |
--------------------------------------------------------------------------------
/addons/netfox.internals/ring-buffer.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cmfbs3cx33lkw
2 |
--------------------------------------------------------------------------------
/addons/netfox.noray/packet-handshake.gd.uid:
--------------------------------------------------------------------------------
1 | uid://tt3j25a05n7c
2 |
--------------------------------------------------------------------------------
/addons/netfox.noray/protocol-handler.gd.uid:
--------------------------------------------------------------------------------
1 | uid://ou0pi8ebvqsi
2 |
--------------------------------------------------------------------------------
/addons/netfox/network-performance.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cqgf7aqmjfoa3
2 |
--------------------------------------------------------------------------------
/addons/netfox/peer-visibility-filter.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cp3pkerfb0mxk
2 |
--------------------------------------------------------------------------------
/addons/netfox/time/network-clocks.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dnp07ns6cpc70
2 |
--------------------------------------------------------------------------------
/addons/netfox.extras/weapon/network-weapon.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cill8uva6thl5
2 |
--------------------------------------------------------------------------------
/addons/netfox.internals/history-buffer.gd.uid:
--------------------------------------------------------------------------------
1 | uid://btuvvlw3jkx2e
2 |
--------------------------------------------------------------------------------
/addons/netfox/encoder/diff-history-encoder.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dc73evbedbmvs
2 |
--------------------------------------------------------------------------------
/addons/netfox/network-time-synchronizer.gd.uid:
--------------------------------------------------------------------------------
1 | uid://5rmn2ik3kjo8
2 |
--------------------------------------------------------------------------------
/addons/netfox/properties/property-cache.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cjb41satp02xy
2 |
--------------------------------------------------------------------------------
/addons/netfox/properties/property-config.gd.uid:
--------------------------------------------------------------------------------
1 | uid://b58ojksbbkrvh
2 |
--------------------------------------------------------------------------------
/addons/netfox/properties/property-entry.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cixo40ot0fqqv
2 |
--------------------------------------------------------------------------------
/addons/netfox/properties/property-snapshot.gd.uid:
--------------------------------------------------------------------------------
1 | uid://d2dgbafx6338f
2 |
--------------------------------------------------------------------------------
/addons/netfox/rollback/network-rollback.gd.uid:
--------------------------------------------------------------------------------
1 | uid://gas743comroj
2 |
--------------------------------------------------------------------------------
/addons/netfox/time/network-clock-sample.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cdmch34dx2uhe
2 |
--------------------------------------------------------------------------------
/addons/debug_draw_3d/debug_draw_3d.gdextension.uid:
--------------------------------------------------------------------------------
1 | uid://bfmlrrm0oq77g
2 |
--------------------------------------------------------------------------------
/addons/netfox.extras/physics/godot_driver_2d.gd.uid:
--------------------------------------------------------------------------------
1 | uid://c8p8gymii2y5v
2 |
--------------------------------------------------------------------------------
/addons/netfox.extras/physics/godot_driver_3d.gd.uid:
--------------------------------------------------------------------------------
1 | uid://wr5ur5dqrkni
2 |
--------------------------------------------------------------------------------
/addons/netfox.extras/physics/physics_driver.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dcws6qk4hxtun
2 |
--------------------------------------------------------------------------------
/addons/netfox.extras/weapon/network-weapon-2d.gd.uid:
--------------------------------------------------------------------------------
1 | uid://c8pw311ku24we
2 |
--------------------------------------------------------------------------------
/addons/netfox.extras/weapon/network-weapon-3d.gd.uid:
--------------------------------------------------------------------------------
1 | uid://c1xyp6tx4hf3p
2 |
--------------------------------------------------------------------------------
/addons/netfox.extras/weapon/network-weapon-proxy.gd.uid:
--------------------------------------------------------------------------------
1 | uid://76n8gu7udnum
2 |
--------------------------------------------------------------------------------
/addons/netfox/encoder/redundant-history-encoder.gd.uid:
--------------------------------------------------------------------------------
1 | uid://ytn07qahpatv
2 |
--------------------------------------------------------------------------------
/addons/netfox/encoder/snapshot-history-encoder.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dwhlghjsgdy4o
2 |
--------------------------------------------------------------------------------
/addons/netfox/rollback/rollback-freshness-store.gd.uid:
--------------------------------------------------------------------------------
1 | uid://bjpcq3jhfbqeh
2 |
--------------------------------------------------------------------------------
/addons/netfox/rollback/rollback-synchronizer.gd.uid:
--------------------------------------------------------------------------------
1 | uid://d350u8evihs1u
2 |
--------------------------------------------------------------------------------
/addons/netfox/serializers/tickset-serializer.gd.uid:
--------------------------------------------------------------------------------
1 | uid://b88vxloxp2wff
2 |
--------------------------------------------------------------------------------
/addons/netfox/time/network-tickrate-handshake.gd.uid:
--------------------------------------------------------------------------------
1 | uid://ododt6lhpbvq
2 |
--------------------------------------------------------------------------------
/addons/netfox.extras/physics/network-rigid-body-2d.gd.uid:
--------------------------------------------------------------------------------
1 | uid://c8hw7ol53m55g
2 |
--------------------------------------------------------------------------------
/addons/netfox.extras/physics/network-rigid-body-3d.gd.uid:
--------------------------------------------------------------------------------
1 | uid://bklxcdyxgbjg2
2 |
--------------------------------------------------------------------------------
/addons/netfox.extras/physics/physics-driver-toggles.gd.uid:
--------------------------------------------------------------------------------
1 | uid://bu4ppfj0ovkbr
2 |
--------------------------------------------------------------------------------
/addons/netfox.extras/physics/rapier_driver_2d.gd.uid.off:
--------------------------------------------------------------------------------
1 | uid://bcc28dvk0pufe
2 |
--------------------------------------------------------------------------------
/addons/netfox.extras/physics/rapier_driver_3d.gd.uid.off:
--------------------------------------------------------------------------------
1 | uid://bo4lplbeepibj
2 |
--------------------------------------------------------------------------------
/addons/netfox.extras/state-machine/rewindable-state.gd.uid:
--------------------------------------------------------------------------------
1 | uid://usyufdtn83hc
2 |
--------------------------------------------------------------------------------
/addons/netfox/properties/property-history-buffer.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dlkog3qntq03x
2 |
--------------------------------------------------------------------------------
/addons/netfox.extras/rewindable-random-number-generator.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cqj8glnpkt5po
2 |
--------------------------------------------------------------------------------
/addons/netfox.extras/weapon/network-weapon-hitscan-3d.gd.uid:
--------------------------------------------------------------------------------
1 | uid://daf6uabdxsfwq
2 |
--------------------------------------------------------------------------------
/addons/netfox.extras/state-machine/rewindable-state-machine.gd.uid:
--------------------------------------------------------------------------------
1 | uid://byrgwv2o7hstx
2 |
--------------------------------------------------------------------------------
/addons/netfox/rollback/composite/rollback-history-recorder.gd.uid:
--------------------------------------------------------------------------------
1 | uid://bn6fsqxbfhihk
2 |
--------------------------------------------------------------------------------
/addons/netfox/rollback/composite/rollback-history-transmitter.gd.uid:
--------------------------------------------------------------------------------
1 | uid://ofadsxmvd3e3
2 |
--------------------------------------------------------------------------------
/models/car.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/models/car.glb
--------------------------------------------------------------------------------
/models/car.blend:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/models/car.blend
--------------------------------------------------------------------------------
/models/car.blend1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/models/car.blend1
--------------------------------------------------------------------------------
/models/field.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/models/field.glb
--------------------------------------------------------------------------------
/models/field.blend:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/models/field.blend
--------------------------------------------------------------------------------
/models/field.blend1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/models/field.blend1
--------------------------------------------------------------------------------
/materials/ball_orm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/materials/ball_orm.png
--------------------------------------------------------------------------------
/materials/wall_orm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/materials/wall_orm.png
--------------------------------------------------------------------------------
/materials/wall_sss.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/materials/wall_sss.png
--------------------------------------------------------------------------------
/materials/grass_orm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/materials/grass_orm.png
--------------------------------------------------------------------------------
/materials/ball_albedo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/materials/ball_albedo.png
--------------------------------------------------------------------------------
/materials/ball_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/materials/ball_normal.png
--------------------------------------------------------------------------------
/materials/grass_albedo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/materials/grass_albedo.png
--------------------------------------------------------------------------------
/materials/grass_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/materials/grass_normal.png
--------------------------------------------------------------------------------
/materials/wall_albedo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/materials/wall_albedo.png
--------------------------------------------------------------------------------
/materials/wall_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/materials/wall_normal.png
--------------------------------------------------------------------------------
/materials/ball_heightmap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/materials/ball_heightmap.png
--------------------------------------------------------------------------------
/materials/grass_emission.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/materials/grass_emission.png
--------------------------------------------------------------------------------
/materials/grass_heightmap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/materials/grass_heightmap.png
--------------------------------------------------------------------------------
/materials/wall_heightmap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/materials/wall_heightmap.png
--------------------------------------------------------------------------------
/scripts/confetti.gd:
--------------------------------------------------------------------------------
1 | extends GPUParticles3D
2 |
3 | func _ready() -> void:
4 | emitting = true
5 |
6 | func _on_finished() -> void:
7 | queue_free()
8 |
--------------------------------------------------------------------------------
/addons/debug_draw_3d/libs/libdd3d.linux.editor.x86_64.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/addons/debug_draw_3d/libs/libdd3d.linux.editor.x86_64.so
--------------------------------------------------------------------------------
/addons/debug_draw_3d/libs/libdd3d.windows.editor.x86_64.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/addons/debug_draw_3d/libs/libdd3d.windows.editor.x86_64.dll
--------------------------------------------------------------------------------
/addons/debug_draw_3d/libs/libdd3d.web.template_debug.wasm32.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/addons/debug_draw_3d/libs/libdd3d.web.template_debug.wasm32.wasm
--------------------------------------------------------------------------------
/addons/debug_draw_3d/libs/libdd3d.android.template_debug.arm32.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/addons/debug_draw_3d/libs/libdd3d.android.template_debug.arm32.so
--------------------------------------------------------------------------------
/addons/debug_draw_3d/libs/libdd3d.android.template_debug.arm64.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/addons/debug_draw_3d/libs/libdd3d.android.template_debug.arm64.so
--------------------------------------------------------------------------------
/addons/debug_draw_3d/libs/libdd3d.android.template_debug.x86_32.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/addons/debug_draw_3d/libs/libdd3d.android.template_debug.x86_32.so
--------------------------------------------------------------------------------
/addons/debug_draw_3d/libs/libdd3d.android.template_debug.x86_64.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/addons/debug_draw_3d/libs/libdd3d.android.template_debug.x86_64.so
--------------------------------------------------------------------------------
/addons/debug_draw_3d/libs/libdd3d.android.template_release.arm32.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/addons/debug_draw_3d/libs/libdd3d.android.template_release.arm32.so
--------------------------------------------------------------------------------
/addons/debug_draw_3d/libs/libdd3d.android.template_release.arm64.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/addons/debug_draw_3d/libs/libdd3d.android.template_release.arm64.so
--------------------------------------------------------------------------------
/addons/debug_draw_3d/libs/libdd3d.linux.template_release.x86_64.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/addons/debug_draw_3d/libs/libdd3d.linux.template_release.x86_64.so
--------------------------------------------------------------------------------
/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.wasm
--------------------------------------------------------------------------------
/addons/debug_draw_3d/libs/libdd3d.android.template_release.x86_32.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/addons/debug_draw_3d/libs/libdd3d.android.template_release.x86_32.so
--------------------------------------------------------------------------------
/addons/debug_draw_3d/libs/libdd3d.android.template_release.x86_64.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/addons/debug_draw_3d/libs/libdd3d.android.template_release.x86_64.so
--------------------------------------------------------------------------------
/addons/debug_draw_3d/libs/libdd3d.ios.template_debug.universal.dylib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/addons/debug_draw_3d/libs/libdd3d.ios.template_debug.universal.dylib
--------------------------------------------------------------------------------
/addons/debug_draw_3d/libs/libdd3d.windows.template_release.x86_64.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/addons/debug_draw_3d/libs/libdd3d.windows.template_release.x86_64.dll
--------------------------------------------------------------------------------
/addons/netfox/plugin.cfg:
--------------------------------------------------------------------------------
1 | [plugin]
2 |
3 | name="netfox"
4 | description="Shared internals for netfox addons"
5 | author="Tamas Galffy and contributors"
6 | version="1.33.1"
7 | script="netfox.gd"
8 |
--------------------------------------------------------------------------------
/addons/debug_draw_3d/libs/libdd3d.ios.template_release.universal.dylib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/addons/debug_draw_3d/libs/libdd3d.ios.template_release.universal.dylib
--------------------------------------------------------------------------------
/addons/debug_draw_3d/libs/libdd3d.web.template_debug.wasm32.threads.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/addons/debug_draw_3d/libs/libdd3d.web.template_debug.wasm32.threads.wasm
--------------------------------------------------------------------------------
/addons/debug_draw_3d/libs/libdd3d.linux.template_release.x86_64.enabled.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/addons/debug_draw_3d/libs/libdd3d.linux.template_release.x86_64.enabled.so
--------------------------------------------------------------------------------
/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.enabled.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.enabled.wasm
--------------------------------------------------------------------------------
/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.threads.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.threads.wasm
--------------------------------------------------------------------------------
/addons/debug_draw_3d/libs/libdd3d.windows.template_release.x86_64.enabled.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/addons/debug_draw_3d/libs/libdd3d.windows.template_release.x86_64.enabled.dll
--------------------------------------------------------------------------------
/addons/debug_draw_3d/libs/libdd3d.ios.template_release.universal.enabled.dylib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/addons/debug_draw_3d/libs/libdd3d.ios.template_release.universal.enabled.dylib
--------------------------------------------------------------------------------
/addons/netfox.extras/plugin.cfg:
--------------------------------------------------------------------------------
1 | [plugin]
2 |
3 | name="netfox.extras"
4 | description="Game-specific utilities for Netfox"
5 | author="Tamas Galffy and contributors"
6 | version="1.33.1"
7 | script="netfox-extras.gd"
8 |
--------------------------------------------------------------------------------
/addons/netfox.internals/plugin.cfg:
--------------------------------------------------------------------------------
1 | [plugin]
2 |
3 | name="netfox.internals"
4 | description="Shared internals for netfox addons"
5 | author="Tamas Galffy and contributors"
6 | version="1.33.1"
7 | script="plugin.gd"
8 |
--------------------------------------------------------------------------------
/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.threads.enabled.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.threads.enabled.wasm
--------------------------------------------------------------------------------
/addons/netfox.noray/plugin.cfg:
--------------------------------------------------------------------------------
1 | [plugin]
2 |
3 | name="netfox.noray"
4 | description="Bulletproof your connectivity with noray integration for netfox"
5 | author="Tamas Galffy and contributors"
6 | version="1.33.1"
7 | script="netfox-noray.gd"
8 |
--------------------------------------------------------------------------------
/materials/car_paint.tres:
--------------------------------------------------------------------------------
1 | [gd_resource type="StandardMaterial3D" format=3 uid="uid://b6yeasaavorsj"]
2 |
3 | [resource]
4 | cull_mode = 2
5 | albedo_color = Color(0.913096, 0.098798, 0, 1)
6 | metallic = 0.75
7 | roughness = 0.2
8 | clearcoat_enabled = true
9 |
--------------------------------------------------------------------------------
/scenes/car_ai.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=2 format=3 uid="uid://26d7swkp18du"]
2 |
3 | [ext_resource type="Script" uid="uid://ddst71w41ihgn" path="res://scripts/car_ai.gd" id="1_64sb6"]
4 |
5 | [node name="Car_AI" type="Node3D"]
6 | script = ExtResource("1_64sb6")
7 |
--------------------------------------------------------------------------------
/addons/debug_draw_3d/libs/libdd3d.macos.editor.universal.framework/libdd3d.macos.editor.universal.dylib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/addons/debug_draw_3d/libs/libdd3d.macos.editor.universal.framework/libdd3d.macos.editor.universal.dylib
--------------------------------------------------------------------------------
/addons/netfox.internals/README.md:
--------------------------------------------------------------------------------
1 | # netfox.internals
2 |
3 | Shared utilities for [netfox] addons. Not intended for standalone usage.
4 |
5 | Instead, check out the other addons in the [netfox] repository.
6 |
7 |
8 | [netfox]: https://github.com/foxssake/netfox
9 |
--------------------------------------------------------------------------------
/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.framework/libdd3d.macos.template_release.universal.dylib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.framework/libdd3d.macos.template_release.universal.dylib
--------------------------------------------------------------------------------
/scripts/goal.gd:
--------------------------------------------------------------------------------
1 | extends Area3D
2 |
3 | func _ready():
4 | NetworkRollback.on_process_tick.connect(on_rollback_tick)
5 |
6 | func on_rollback_tick(tick : int) -> void:
7 |
8 | for body in get_overlapping_bodies():
9 | if body is Ball:
10 | body.entered_goal_area(self, tick)
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Godot 4+ specific ignores
2 | .godot/
3 |
4 | # Godot-specific ignores
5 | .import/
6 | export.cfg
7 | export_credentials.cfg
8 |
9 | # Imported translations (automatically generated from CSV files)
10 | *.translation
11 |
12 | # Mono-specific ignores
13 | .mono/
14 | data_*/
15 | mono_crash.*.json
16 |
--------------------------------------------------------------------------------
/materials/car_window.tres:
--------------------------------------------------------------------------------
1 | [gd_resource type="StandardMaterial3D" format=3 uid="uid://d0ug2v7jto7a4"]
2 |
3 | [resource]
4 | cull_mode = 2
5 | albedo_color = Color(0.051223397, 0.1020442, 0.1688985, 1)
6 | metallic = 0.52
7 | roughness = 0.1
8 | clearcoat_enabled = true
9 | clearcoat = 0.05
10 | clearcoat_roughness = 0.25
11 |
--------------------------------------------------------------------------------
/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.enabled.framework/libdd3d.macos.template_release.universal.enabled.dylib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albertok/godot-rocket-league/HEAD/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.enabled.framework/libdd3d.macos.template_release.universal.enabled.dylib
--------------------------------------------------------------------------------
/scripts/input.gd:
--------------------------------------------------------------------------------
1 | extends BaseNetInput
2 | class_name PlayerInput
3 |
4 |
5 | var ai_enabled: bool = false
6 | var ai_motion: Vector2 = Vector2()
7 | var ai_jumping: bool = false
8 | var jumping: bool = false
9 | var motion : Vector2 = Vector2.ZERO
10 |
11 | func _gather():
12 | if is_multiplayer_authority():
13 | if ai_enabled:
14 | motion = ai_motion
15 | jumping = ai_jumping
16 | else:
17 | motion = Input.get_vector("left", "right", "down", "up")
18 | jumping = Input.is_action_pressed("jump")
19 |
--------------------------------------------------------------------------------
/scenes/field.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=4 format=3 uid="uid://u33v8symfbrp"]
2 |
3 | [ext_resource type="PackedScene" uid="uid://th8ke8d8f3dl" path="res://models/field.blend" id="1_6pmjc"]
4 | [ext_resource type="Material" uid="uid://uo7itoedkenq" path="res://materials/wall.tres" id="2_lptdg"]
5 | [ext_resource type="Material" uid="uid://bnqudedf7vlb0" path="res://materials/grass.tres" id="3_7156t"]
6 |
7 | [node name="field" instance=ExtResource("1_6pmjc")]
8 |
9 | [node name="arena" parent="." index="0"]
10 | surface_material_override/0 = ExtResource("2_lptdg")
11 | surface_material_override/1 = ExtResource("3_7156t")
12 |
--------------------------------------------------------------------------------
/scenes/car_model.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=4 format=3 uid="uid://cylxynmfbbjwk"]
2 |
3 | [ext_resource type="PackedScene" uid="uid://mwlmhv68da6e" path="res://models/car.blend" id="1_bg842"]
4 | [ext_resource type="Material" uid="uid://b6yeasaavorsj" path="res://materials/car_paint.tres" id="2_ifs7h"]
5 | [ext_resource type="Material" uid="uid://d0ug2v7jto7a4" path="res://materials/car_window.tres" id="3_hhld5"]
6 |
7 | [node name="car" instance=ExtResource("1_bg842")]
8 |
9 | [node name="Car" parent="." index="0"]
10 | surface_material_override/0 = ExtResource("2_ifs7h")
11 | surface_material_override/1 = ExtResource("3_hhld5")
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # godot-rocket-league
2 |
3 | Network physics with rollback test using Godot and netfox
4 |
5 | Requires [Godot with Stepping PR](https://github.com/albertok/godot) or [Blazium](https://blazium.app/)
6 |
7 | Alternatily build Godot manually with [this pull request](https://github.com/godotengine/godot/pull/76462) applied. See the [Godot documentation](https://docs.godotengine.org/en/stable/contributing/development/compiling/index.html) for compilation instructions.
8 |
9 | The project will auto connect to itself when run. Just set as many run instances as needed.
10 |
11 |
12 | https://github.com/user-attachments/assets/b5319a5b-7460-4949-93e0-223572937ac2
13 |
14 |
--------------------------------------------------------------------------------
/addons/netfox.extras/base-net-input.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 | class_name BaseNetInput
3 |
4 | ## Base class for Input nodes used with rollback.
5 |
6 | func _ready():
7 | NetworkTime.before_tick_loop.connect(func():
8 | if is_multiplayer_authority():
9 | _gather()
10 | )
11 |
12 | ## Method for gathering input.
13 | ##
14 | ## This method is supposed to be overridden with your input logic. The input
15 | ## data itself may be gathered outside of this method ( e.g. gathering it over
16 | ## multiple _process calls ), but this is the point where the input variables
17 | ## must be set.
18 | ##
19 | ## [i]Note:[/i] This is only called for the local player's input nodes.
20 | func _gather():
21 | pass
22 |
--------------------------------------------------------------------------------
/addons/netfox.extras/physics/rapier_driver_2d.gd.off:
--------------------------------------------------------------------------------
1 | extends PhysicsDriver
2 |
3 | class_name RapierDriver2D
4 |
5 | func _init_physics_space() -> void:
6 | physics_space = get_viewport().world_2d.space
7 | PhysicsServer2D.space_set_active(physics_space, false)
8 |
9 | func _physics_step(delta) -> void:
10 | RapierPhysicsServer2D.space_step(physics_space, delta)
11 | RapierPhysicsServer2D.space_flush_queries(physics_space)
12 |
13 | func _snapshot_space(tick: int) -> void:
14 | snapshots[tick] = RapierPhysicsServer2D.export_binary(physics_space)
15 |
16 | func _rollback_space(tick) -> void:
17 | if snapshots.has(tick):
18 | RapierPhysicsServer2D.import_binary(physics_space, snapshots[tick])
19 |
--------------------------------------------------------------------------------
/addons/netfox.extras/physics/rapier_driver_3d.gd.off:
--------------------------------------------------------------------------------
1 | extends PhysicsDriver
2 |
3 | class_name RapierDriver3D
4 |
5 | func _init_physics_space() -> void:
6 | physics_space = get_viewport().world_3d.space
7 | PhysicsServer3D.space_set_active(physics_space, false)
8 |
9 | func _physics_step(delta) -> void:
10 | RapierPhysicsServer3D.space_step(physics_space, delta)
11 | RapierPhysicsServer3D.space_flush_queries(physics_space)
12 |
13 | func _snapshot_space(tick: int) -> void:
14 | snapshots[tick] = RapierPhysicsServer3D.export_binary(physics_space)
15 |
16 | func _rollback_space(tick) -> void:
17 | if snapshots.has(tick):
18 | RapierPhysicsServer3D.import_binary(physics_space, snapshots[tick])
19 |
--------------------------------------------------------------------------------
/addons/netfox.internals/ring-buffer.gd:
--------------------------------------------------------------------------------
1 | extends RefCounted
2 | class_name _RingBuffer
3 |
4 | var _data: Array
5 | var _capacity: int
6 | var _size: int = 0
7 | var _head: int = 0
8 |
9 | func _init(p_capacity: int):
10 | _capacity = p_capacity
11 | _data = []
12 | _data.resize(p_capacity)
13 |
14 | func push(item):
15 | _data[_head] = item
16 |
17 | _size += 1
18 | _head = (_head + 1) % _capacity
19 |
20 | func get_data() -> Array:
21 | if _size < _capacity:
22 | return _data.slice(0, _size)
23 | else:
24 | return _data
25 |
26 | func size() -> int:
27 | return _size
28 |
29 | func is_empty() -> bool:
30 | return _size == 0
31 |
32 | func clear():
33 | _size = 0
34 | _head = 0
35 |
--------------------------------------------------------------------------------
/materials/wall.tres:
--------------------------------------------------------------------------------
1 | [gd_resource type="StandardMaterial3D" load_steps=3 format=3 uid="uid://uo7itoedkenq"]
2 |
3 | [ext_resource type="Texture2D" uid="uid://c5mr1vlj31pmx" path="res://materials/wall_albedo.png" id="1"]
4 | [ext_resource type="Texture2D" uid="uid://cx2hrsvj4gts6" path="res://materials/wall_normal.png" id="3"]
5 |
6 | [resource]
7 | transparency = 1
8 | blend_mode = 1
9 | cull_mode = 2
10 | albedo_color = Color(1, 1, 1, 0.258824)
11 | albedo_texture = ExtResource("1")
12 | metallic_specular = 0.1
13 | roughness = 0.6
14 | normal_enabled = true
15 | normal_texture = ExtResource("3")
16 | clearcoat_enabled = true
17 | anisotropy = -0.66
18 | refraction_scale = 0.25
19 | uv1_triplanar = true
20 |
--------------------------------------------------------------------------------
/addons/netfox/time/network-clock-sample.gd:
--------------------------------------------------------------------------------
1 | extends RefCounted
2 | class_name NetworkClockSample
3 |
4 | var ping_sent: float
5 | var ping_received: float
6 | var pong_sent: float
7 | var pong_received: float
8 |
9 | func get_rtt() -> float:
10 | return pong_received - ping_sent
11 |
12 | func get_offset() -> float:
13 | # See: https://datatracker.ietf.org/doc/html/rfc5905#section-8
14 | # See: https://support.huawei.com/enterprise/en/doc/EDOC1100278232/729da750/ntp-fundamentals
15 | return ((ping_received - ping_sent) + (pong_sent - pong_received)) / 2.
16 |
17 | func _to_string() -> String:
18 | return "(theta=%.2fms; delta=%.2fms; t1=%.4fs; t2=%.4fs; t3=%.4fs; t4=%.4fs)" % [
19 | get_offset() * 1000., get_rtt() * 1000.,
20 | ping_sent, ping_received, pong_sent, pong_received
21 | ]
22 |
--------------------------------------------------------------------------------
/addons/netfox/properties/property-cache.gd:
--------------------------------------------------------------------------------
1 | extends RefCounted
2 | class_name PropertyCache
3 |
4 | var root: Node
5 | var _cache: Dictionary = {}
6 |
7 | static var _logger: _NetfoxLogger = _NetfoxLogger.for_netfox("PropertyCache")
8 |
9 | func _init(p_root: Node):
10 | root = p_root
11 |
12 | func get_entry(path: String) -> PropertyEntry:
13 | if not _cache.has(path):
14 | var parsed = PropertyEntry.parse(root, path)
15 | if not parsed.is_valid():
16 | _logger.warning("Invalid property path: %s", [path])
17 | _cache[path] = parsed
18 | return _cache[path]
19 |
20 | func properties() -> Array:
21 | var result: Array[PropertyEntry]
22 | # Can be slow, but no other way to do this with type-safety
23 | # See: https://github.com/godotengine/godot/issues/72627
24 | result.assign(_cache.values())
25 | return result
26 |
27 | func clear():
28 | _cache.clear()
29 |
--------------------------------------------------------------------------------
/addons/netfox/rollback/rollback-freshness-store.gd:
--------------------------------------------------------------------------------
1 | extends RefCounted
2 | class_name RollbackFreshnessStore
3 |
4 | ## This class tracks nodes and whether they have processed any given tick during
5 | ## a rollback.
6 |
7 | # TODO: _Set
8 | var _data: Dictionary = {}
9 |
10 | func is_fresh(node: Node, tick: int) -> bool:
11 | if not _data.has(tick):
12 | return true
13 |
14 | if not _data[tick].has(node):
15 | return true
16 |
17 | return false
18 |
19 | func notify_processed(node: Node, tick: int) -> void:
20 | if not _data.has(tick):
21 | _data[tick] = {}
22 |
23 | _data[tick][node] = true
24 |
25 | func trim() -> void:
26 | while not _data.is_empty():
27 | var earliest_tick := _data.keys().min()
28 | if earliest_tick < NetworkRollback.history_start:
29 | _data.erase(earliest_tick)
30 | else:
31 | break
32 |
33 | func clear():
34 | _data.clear()
35 |
--------------------------------------------------------------------------------
/addons/netfox.extras/README.md:
--------------------------------------------------------------------------------
1 | # netfox.extras
2 |
3 | High-level, game-specific extras for [netfox]!
4 |
5 | ## Features
6 |
7 | * 🔫 Networked weapons
8 | * ⌨️ Rollback-aware base class for input
9 |
10 | ## Overview
11 |
12 | This addon is pretty much a catch-all project for things that are useful, but
13 | are not core to netfox.
14 |
15 | ## Install
16 |
17 | See the root [README](../../README.md).
18 |
19 | ## Usage
20 |
21 | See the [docs](https://foxssake.github.io/netfox/).
22 |
23 | ## License
24 |
25 | netfox.extras is under the [MIT license](LICENSE).
26 |
27 | ## Issues
28 |
29 | In case of any issues, comments, or questions, please feel free to [open an issue]!
30 |
31 | [netfox]: https://github.com/foxssake/netfox
32 | [source]: https://github.com/foxssake/netfox/archive/refs/heads/main.zip
33 | [open an issue]: https://github.com/foxssake/netfox/issues
34 |
35 |
--------------------------------------------------------------------------------
/scenes/goal.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=4 format=3 uid="uid://bg5hxbj5ib07n"]
2 |
3 | [ext_resource type="Script" uid="uid://cau6jor3sbnjm" path="res://scripts/goal.gd" id="1_jbojv"]
4 |
5 | [sub_resource type="BoxShape3D" id="BoxShape3D_tefeu"]
6 | size = Vector3(15, 7, 4.4282227)
7 |
8 | [sub_resource type="BoxMesh" id="BoxMesh_xl315"]
9 | size = Vector3(15.365, 7.25, 4)
10 |
11 | [node name="Area3D" type="Area3D"]
12 | collision_layer = 8
13 | collision_mask = 4
14 | script = ExtResource("1_jbojv")
15 |
16 | [node name="CollisionShape3D" type="CollisionShape3D" parent="."]
17 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1.7141113)
18 | shape = SubResource("BoxShape3D_tefeu")
19 |
20 | [node name="MeshInstance3D" type="MeshInstance3D" parent="."]
21 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1.6367207)
22 | visible = false
23 | mesh = SubResource("BoxMesh_xl315")
24 |
--------------------------------------------------------------------------------
/scripts/camera_3d.gd:
--------------------------------------------------------------------------------
1 | extends Camera3D
2 |
3 | @export var target: Node3D
4 | @export var height: float = 1.5
5 | @export var target_distance: float = 5.0
6 | @export var smooth_speed: float = 5.0
7 |
8 | var current_position: Vector3
9 | var current_rotation: Basis
10 |
11 | func _process(delta):
12 | if !target:
13 | return
14 |
15 | var target_pos = target.position
16 | var target_direction = target.global_transform.basis.z
17 | var desired_position = target_pos - target_direction * target_distance
18 |
19 | desired_position.y = target_pos.y + height
20 | current_position = current_position.lerp(desired_position, smooth_speed * delta)
21 | position = current_position
22 |
23 | var desired_rotation = Transform3D().looking_at(target_pos - position, Vector3.UP).basis
24 | current_rotation = current_rotation.slerp(desired_rotation, 2. * smooth_speed * delta)
25 | global_transform.basis = current_rotation
26 |
--------------------------------------------------------------------------------
/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/scenes/wheel.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=4 format=3 uid="uid://d2sn826pqsfs5"]
2 |
3 | [ext_resource type="Script" uid="uid://c8tk028y8ex3g" path="res://scripts/wheel.gd" id="1_hm1pg"]
4 |
5 | [sub_resource type="CylinderMesh" id="CylinderMesh_hm1pg"]
6 |
7 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_hm1pg"]
8 | albedo_color = Color(0.0078223, 0.00212023, 0.00175743, 1)
9 | metallic = 0.18
10 | roughness = 0.67
11 |
12 | [node name="RayWheel" type="RayCast3D"]
13 | script = ExtResource("1_hm1pg")
14 | is_front_wheel = true
15 | metadata/_custom_type_script = ExtResource("1_hm1pg")
16 |
17 | [node name="Wheel" type="Node3D" parent="."]
18 |
19 | [node name="MeshInstance3D" type="MeshInstance3D" parent="Wheel"]
20 | transform = Transform3D(-2.18557e-08, 0.1, -2.18557e-08, 0, -4.37114e-09, -0.5, -0.5, -4.37114e-09, 9.55343e-16, 0, 0, 0)
21 | mesh = SubResource("CylinderMesh_hm1pg")
22 | surface_material_override/0 = SubResource("StandardMaterial3D_hm1pg")
23 |
--------------------------------------------------------------------------------
/addons/netfox/properties/property-history-buffer.gd:
--------------------------------------------------------------------------------
1 | extends _HistoryBuffer
2 | class_name _PropertyHistoryBuffer
3 |
4 | func get_snapshot(tick: int) -> _PropertySnapshot:
5 | if _buffer.has(tick):
6 | return _buffer[tick]
7 | else:
8 | return _PropertySnapshot.new()
9 |
10 | func set_snapshot(tick: int, data) -> void:
11 | if data is Dictionary:
12 | var snapshot := _PropertySnapshot.from_dictionary(data)
13 | super(tick, snapshot)
14 | elif data is _PropertySnapshot:
15 | super(tick, data)
16 | else:
17 | push_error("Data not a PropertSnapshot! %s" % [data])
18 |
19 | func get_history(tick: int) -> _PropertySnapshot:
20 | var snapshot = super(tick)
21 |
22 | return snapshot if snapshot else _PropertySnapshot.new()
23 |
24 | func trim(earliest_tick_to_keep: int = NetworkRollback.history_start) -> void:
25 | super(earliest_tick_to_keep)
26 |
27 | func merge(data: _PropertySnapshot, tick:int) -> void:
28 | set_snapshot(tick, get_snapshot(tick).merge(data))
29 |
--------------------------------------------------------------------------------
/materials/wall_orm.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://do01ofepfaxw7"
6 | path.s3tc="res://.godot/imported/wall_orm.png-9cb994fd10e71375a45c01f4089091a6.s3tc.ctex"
7 | metadata={
8 | "imported_formats": ["s3tc_bptc"],
9 | "vram_texture": true
10 | }
11 |
12 | [deps]
13 |
14 | source_file="res://materials/wall_orm.png"
15 | dest_files=["res://.godot/imported/wall_orm.png-9cb994fd10e71375a45c01f4089091a6.s3tc.ctex"]
16 |
17 | [params]
18 |
19 | compress/mode=2
20 | compress/high_quality=false
21 | compress/lossy_quality=0.7
22 | compress/hdr_compression=1
23 | compress/normal_map=0
24 | compress/channel_pack=0
25 | mipmaps/generate=true
26 | mipmaps/limit=-1
27 | roughness/mode=0
28 | roughness/src_normal=""
29 | process/fix_alpha_border=true
30 | process/premult_alpha=false
31 | process/normal_map_invert_y=false
32 | process/hdr_as_srgb=false
33 | process/hdr_clamp_exposure=false
34 | process/size_limit=0
35 | detect_3d/compress_to=0
36 |
--------------------------------------------------------------------------------
/materials/ball_albedo.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://cc4mhe4g0ymsv"
6 | path.s3tc="res://.godot/imported/ball_albedo.png-23bc2e478e4a5041db669e7b2c7af09b.s3tc.ctex"
7 | metadata={
8 | "imported_formats": ["s3tc_bptc"],
9 | "vram_texture": true
10 | }
11 |
12 | [deps]
13 |
14 | source_file="res://materials/ball_albedo.png"
15 | dest_files=["res://.godot/imported/ball_albedo.png-23bc2e478e4a5041db669e7b2c7af09b.s3tc.ctex"]
16 |
17 | [params]
18 |
19 | compress/mode=2
20 | compress/high_quality=false
21 | compress/lossy_quality=0.7
22 | compress/hdr_compression=1
23 | compress/normal_map=0
24 | compress/channel_pack=0
25 | mipmaps/generate=true
26 | mipmaps/limit=-1
27 | roughness/mode=0
28 | roughness/src_normal=""
29 | process/fix_alpha_border=true
30 | process/premult_alpha=false
31 | process/normal_map_invert_y=false
32 | process/hdr_as_srgb=false
33 | process/hdr_clamp_exposure=false
34 | process/size_limit=0
35 | detect_3d/compress_to=0
36 |
--------------------------------------------------------------------------------
/materials/wall_albedo.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://c5mr1vlj31pmx"
6 | path.s3tc="res://.godot/imported/wall_albedo.png-92df8abddd5c146147a53f5a4eb63c26.s3tc.ctex"
7 | metadata={
8 | "imported_formats": ["s3tc_bptc"],
9 | "vram_texture": true
10 | }
11 |
12 | [deps]
13 |
14 | source_file="res://materials/wall_albedo.png"
15 | dest_files=["res://.godot/imported/wall_albedo.png-92df8abddd5c146147a53f5a4eb63c26.s3tc.ctex"]
16 |
17 | [params]
18 |
19 | compress/mode=2
20 | compress/high_quality=false
21 | compress/lossy_quality=0.7
22 | compress/hdr_compression=1
23 | compress/normal_map=0
24 | compress/channel_pack=0
25 | mipmaps/generate=true
26 | mipmaps/limit=-1
27 | roughness/mode=0
28 | roughness/src_normal=""
29 | process/fix_alpha_border=true
30 | process/premult_alpha=false
31 | process/normal_map_invert_y=false
32 | process/hdr_as_srgb=false
33 | process/hdr_clamp_exposure=false
34 | process/size_limit=0
35 | detect_3d/compress_to=0
36 |
--------------------------------------------------------------------------------
/icon.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://j8o8c4022asy"
6 | path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://icon.svg"
14 | dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 | svg/scale=1.0
36 | editor/scale_with_editor_scale=false
37 | editor/convert_colors_with_editor_theme=false
38 |
--------------------------------------------------------------------------------
/materials/grass_emission.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://c5xh5cxrpjwps"
6 | path.s3tc="res://.godot/imported/grass_emission.png-66e51b9b1c972bc85fe0f9dea90941b9.s3tc.ctex"
7 | metadata={
8 | "imported_formats": ["s3tc_bptc"],
9 | "vram_texture": true
10 | }
11 |
12 | [deps]
13 |
14 | source_file="res://materials/grass_emission.png"
15 | dest_files=["res://.godot/imported/grass_emission.png-66e51b9b1c972bc85fe0f9dea90941b9.s3tc.ctex"]
16 |
17 | [params]
18 |
19 | compress/mode=2
20 | compress/high_quality=false
21 | compress/lossy_quality=0.7
22 | compress/hdr_compression=1
23 | compress/normal_map=0
24 | compress/channel_pack=0
25 | mipmaps/generate=true
26 | mipmaps/limit=-1
27 | roughness/mode=0
28 | roughness/src_normal=""
29 | process/fix_alpha_border=true
30 | process/premult_alpha=false
31 | process/normal_map_invert_y=false
32 | process/hdr_as_srgb=false
33 | process/hdr_clamp_exposure=false
34 | process/size_limit=0
35 | detect_3d/compress_to=0
36 |
--------------------------------------------------------------------------------
/materials/ball_orm.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://dyh6m3p6ofsrk"
6 | path.s3tc="res://.godot/imported/ball_orm.png-23e20c1f54827495554478d51b200a69.s3tc.ctex"
7 | metadata={
8 | "imported_formats": ["s3tc_bptc"],
9 | "vram_texture": true
10 | }
11 |
12 | [deps]
13 |
14 | source_file="res://materials/ball_orm.png"
15 | dest_files=["res://.godot/imported/ball_orm.png-23e20c1f54827495554478d51b200a69.s3tc.ctex"]
16 |
17 | [params]
18 |
19 | compress/mode=2
20 | compress/high_quality=false
21 | compress/lossy_quality=0.7
22 | compress/hdr_compression=1
23 | compress/normal_map=0
24 | compress/channel_pack=0
25 | mipmaps/generate=true
26 | mipmaps/limit=-1
27 | roughness/mode=8
28 | roughness/src_normal="res://materials/ball_normal.png"
29 | process/fix_alpha_border=true
30 | process/premult_alpha=false
31 | process/normal_map_invert_y=false
32 | process/hdr_as_srgb=false
33 | process/hdr_clamp_exposure=false
34 | process/size_limit=0
35 | detect_3d/compress_to=0
36 |
--------------------------------------------------------------------------------
/materials/wall_sss.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://bhlkiifjxmqv0"
6 | path.s3tc="res://.godot/imported/wall_sss.png-2f58901e00eae34ea094f9c92f8bdef3.s3tc.ctex"
7 | metadata={
8 | "imported_formats": ["s3tc_bptc"],
9 | "vram_texture": true
10 | }
11 |
12 | [deps]
13 |
14 | source_file="res://materials/wall_sss.png"
15 | dest_files=["res://.godot/imported/wall_sss.png-2f58901e00eae34ea094f9c92f8bdef3.s3tc.ctex"]
16 |
17 | [params]
18 |
19 | compress/mode=2
20 | compress/high_quality=false
21 | compress/lossy_quality=0.7
22 | compress/hdr_compression=1
23 | compress/normal_map=0
24 | compress/channel_pack=0
25 | mipmaps/generate=true
26 | mipmaps/limit=-1
27 | roughness/mode=8
28 | roughness/src_normal="res://materials/wall_normal.png"
29 | process/fix_alpha_border=true
30 | process/premult_alpha=false
31 | process/normal_map_invert_y=false
32 | process/hdr_as_srgb=false
33 | process/hdr_clamp_exposure=false
34 | process/size_limit=0
35 | detect_3d/compress_to=0
36 |
--------------------------------------------------------------------------------
/addons/netfox.noray/protocol-handler.gd:
--------------------------------------------------------------------------------
1 | extends RefCounted
2 | class_name NorayProtocolHandler
3 | ## This class parses incoming data from noray's protocol.
4 | ##
5 | ## Unless you're writing your own noray integration, [Noray] should cover most
6 | ## use cases.
7 |
8 | ## Emitted for every command parsed during a [method ingest] call.
9 | signal on_command(command: String, data: String)
10 |
11 | var _strbuf: String = ""
12 |
13 | ## Resets the parser.
14 | func reset():
15 | _strbuf = ""
16 |
17 | ## Parse an incoming piece of data.
18 | func ingest(data: String):
19 | _strbuf += data
20 | if not _strbuf.contains("\n"):
21 | return
22 |
23 | var idx = _strbuf.rfind("\n")
24 | var lines = _strbuf.substr(0, idx).split("\n", false)
25 | _strbuf = _strbuf.erase(0, idx + 1)
26 |
27 | for line in lines:
28 | if not line.contains(" "):
29 | on_command.emit(line, "")
30 | else:
31 | var parts = line.split(" ")
32 | var command = parts[0]
33 | var param = parts[1]
34 | on_command.emit(command, param)
35 |
--------------------------------------------------------------------------------
/materials/grass_orm.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://dys4dhkhk3pw4"
6 | path.s3tc="res://.godot/imported/grass_orm.png-84269e06661ad64981d8df14f60aab6f.s3tc.ctex"
7 | metadata={
8 | "imported_formats": ["s3tc_bptc"],
9 | "vram_texture": true
10 | }
11 |
12 | [deps]
13 |
14 | source_file="res://materials/grass_orm.png"
15 | dest_files=["res://.godot/imported/grass_orm.png-84269e06661ad64981d8df14f60aab6f.s3tc.ctex"]
16 |
17 | [params]
18 |
19 | compress/mode=2
20 | compress/high_quality=false
21 | compress/lossy_quality=0.7
22 | compress/hdr_compression=1
23 | compress/normal_map=0
24 | compress/channel_pack=0
25 | mipmaps/generate=true
26 | mipmaps/limit=-1
27 | roughness/mode=8
28 | roughness/src_normal="res://materials/grass_normal.png"
29 | process/fix_alpha_border=true
30 | process/premult_alpha=false
31 | process/normal_map_invert_y=false
32 | process/hdr_as_srgb=false
33 | process/hdr_clamp_exposure=false
34 | process/size_limit=0
35 | detect_3d/compress_to=0
36 |
--------------------------------------------------------------------------------
/materials/ball_normal.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://d3u7ar71ppy7g"
6 | path.s3tc="res://.godot/imported/ball_normal.png-252e7d9ccda3b792119d325f025d4a29.s3tc.ctex"
7 | metadata={
8 | "imported_formats": ["s3tc_bptc"],
9 | "vram_texture": true
10 | }
11 |
12 | [deps]
13 |
14 | source_file="res://materials/ball_normal.png"
15 | dest_files=["res://.godot/imported/ball_normal.png-252e7d9ccda3b792119d325f025d4a29.s3tc.ctex"]
16 |
17 | [params]
18 |
19 | compress/mode=2
20 | compress/high_quality=false
21 | compress/lossy_quality=0.7
22 | compress/hdr_compression=1
23 | compress/normal_map=1
24 | compress/channel_pack=0
25 | mipmaps/generate=true
26 | mipmaps/limit=-1
27 | roughness/mode=1
28 | roughness/src_normal="res://materials/ball_normal.png"
29 | process/fix_alpha_border=true
30 | process/premult_alpha=false
31 | process/normal_map_invert_y=false
32 | process/hdr_as_srgb=false
33 | process/hdr_clamp_exposure=false
34 | process/size_limit=0
35 | detect_3d/compress_to=0
36 |
--------------------------------------------------------------------------------
/materials/grass_normal.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://mr7pu5igx84m"
6 | path.s3tc="res://.godot/imported/grass_normal.png-9ca7d478cc42ea67cb84e4cb5cb3502c.s3tc.ctex"
7 | metadata={
8 | "imported_formats": ["s3tc_bptc"],
9 | "vram_texture": true
10 | }
11 |
12 | [deps]
13 |
14 | source_file="res://materials/grass_normal.png"
15 | dest_files=["res://.godot/imported/grass_normal.png-9ca7d478cc42ea67cb84e4cb5cb3502c.s3tc.ctex"]
16 |
17 | [params]
18 |
19 | compress/mode=2
20 | compress/high_quality=false
21 | compress/lossy_quality=0.7
22 | compress/hdr_compression=1
23 | compress/normal_map=1
24 | compress/channel_pack=0
25 | mipmaps/generate=true
26 | mipmaps/limit=-1
27 | roughness/mode=1
28 | roughness/src_normal="res://materials/grass_normal.png"
29 | process/fix_alpha_border=true
30 | process/premult_alpha=false
31 | process/normal_map_invert_y=false
32 | process/hdr_as_srgb=false
33 | process/hdr_clamp_exposure=false
34 | process/size_limit=0
35 | detect_3d/compress_to=0
36 |
--------------------------------------------------------------------------------
/materials/wall_normal.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://cx2hrsvj4gts6"
6 | path.s3tc="res://.godot/imported/wall_normal.png-76cd383eb0ce2ba8c55283da7c556b73.s3tc.ctex"
7 | metadata={
8 | "imported_formats": ["s3tc_bptc"],
9 | "vram_texture": true
10 | }
11 |
12 | [deps]
13 |
14 | source_file="res://materials/wall_normal.png"
15 | dest_files=["res://.godot/imported/wall_normal.png-76cd383eb0ce2ba8c55283da7c556b73.s3tc.ctex"]
16 |
17 | [params]
18 |
19 | compress/mode=2
20 | compress/high_quality=false
21 | compress/lossy_quality=0.7
22 | compress/hdr_compression=1
23 | compress/normal_map=1
24 | compress/channel_pack=0
25 | mipmaps/generate=true
26 | mipmaps/limit=-1
27 | roughness/mode=1
28 | roughness/src_normal="res://materials/wall_normal.png"
29 | process/fix_alpha_border=true
30 | process/premult_alpha=false
31 | process/normal_map_invert_y=false
32 | process/hdr_as_srgb=false
33 | process/hdr_clamp_exposure=false
34 | process/size_limit=0
35 | detect_3d/compress_to=0
36 |
--------------------------------------------------------------------------------
/materials/ball_heightmap.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://6y5nxpmq7pwg"
6 | path.s3tc="res://.godot/imported/ball_heightmap.png-f5c751a3e170d9d66278679e5d878fab.s3tc.ctex"
7 | metadata={
8 | "imported_formats": ["s3tc_bptc"],
9 | "vram_texture": true
10 | }
11 |
12 | [deps]
13 |
14 | source_file="res://materials/ball_heightmap.png"
15 | dest_files=["res://.godot/imported/ball_heightmap.png-f5c751a3e170d9d66278679e5d878fab.s3tc.ctex"]
16 |
17 | [params]
18 |
19 | compress/mode=2
20 | compress/high_quality=false
21 | compress/lossy_quality=0.7
22 | compress/hdr_compression=1
23 | compress/normal_map=0
24 | compress/channel_pack=0
25 | mipmaps/generate=true
26 | mipmaps/limit=-1
27 | roughness/mode=7
28 | roughness/src_normal="res://materials/ball_normal.png"
29 | process/fix_alpha_border=true
30 | process/premult_alpha=false
31 | process/normal_map_invert_y=false
32 | process/hdr_as_srgb=false
33 | process/hdr_clamp_exposure=false
34 | process/size_limit=0
35 | detect_3d/compress_to=0
36 |
--------------------------------------------------------------------------------
/materials/wall_heightmap.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://cl1qimhntjbv0"
6 | path.s3tc="res://.godot/imported/wall_heightmap.png-200fd14a8b01844a53e52bae6eebb625.s3tc.ctex"
7 | metadata={
8 | "imported_formats": ["s3tc_bptc"],
9 | "vram_texture": true
10 | }
11 |
12 | [deps]
13 |
14 | source_file="res://materials/wall_heightmap.png"
15 | dest_files=["res://.godot/imported/wall_heightmap.png-200fd14a8b01844a53e52bae6eebb625.s3tc.ctex"]
16 |
17 | [params]
18 |
19 | compress/mode=2
20 | compress/high_quality=false
21 | compress/lossy_quality=0.7
22 | compress/hdr_compression=1
23 | compress/normal_map=0
24 | compress/channel_pack=0
25 | mipmaps/generate=true
26 | mipmaps/limit=-1
27 | roughness/mode=7
28 | roughness/src_normal="res://materials/wall_normal.png"
29 | process/fix_alpha_border=true
30 | process/premult_alpha=false
31 | process/normal_map_invert_y=false
32 | process/hdr_as_srgb=false
33 | process/hdr_clamp_exposure=false
34 | process/size_limit=0
35 | detect_3d/compress_to=0
36 |
--------------------------------------------------------------------------------
/materials/grass_heightmap.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://cx8yeg0tx1e4h"
6 | path.s3tc="res://.godot/imported/grass_heightmap.png-1b10dd55170cbc5fe138803a9260c0ce.s3tc.ctex"
7 | metadata={
8 | "imported_formats": ["s3tc_bptc"],
9 | "vram_texture": true
10 | }
11 |
12 | [deps]
13 |
14 | source_file="res://materials/grass_heightmap.png"
15 | dest_files=["res://.godot/imported/grass_heightmap.png-1b10dd55170cbc5fe138803a9260c0ce.s3tc.ctex"]
16 |
17 | [params]
18 |
19 | compress/mode=2
20 | compress/high_quality=false
21 | compress/lossy_quality=0.7
22 | compress/hdr_compression=1
23 | compress/normal_map=0
24 | compress/channel_pack=0
25 | mipmaps/generate=true
26 | mipmaps/limit=-1
27 | roughness/mode=7
28 | roughness/src_normal="res://materials/grass_normal.png"
29 | process/fix_alpha_border=true
30 | process/premult_alpha=false
31 | process/normal_map_invert_y=false
32 | process/hdr_as_srgb=false
33 | process/hdr_clamp_exposure=false
34 | process/size_limit=0
35 | detect_3d/compress_to=0
36 |
--------------------------------------------------------------------------------
/addons/netfox.internals/plugin.gd:
--------------------------------------------------------------------------------
1 | @tool
2 | extends EditorPlugin
3 |
4 | var SETTINGS = [
5 | _NetfoxLogger.make_setting("netfox/logging/log_level")
6 | ]
7 |
8 | func _enter_tree():
9 | for setting in SETTINGS:
10 | add_setting(setting)
11 |
12 | func _exit_tree():
13 | if ProjectSettings.get_setting("netfox/general/clear_settings", false):
14 | for setting in SETTINGS:
15 | remove_setting(setting)
16 |
17 | func add_setting(setting: Dictionary):
18 | if ProjectSettings.has_setting(setting.name):
19 | return
20 |
21 | ProjectSettings.set_setting(setting.name, setting.value)
22 | ProjectSettings.set_initial_value(setting.name, setting.value)
23 | ProjectSettings.add_property_info({
24 | "name": setting.get("name"),
25 | "type": setting.get("type"),
26 | "hint": setting.get("hint", PROPERTY_HINT_NONE),
27 | "hint_string": setting.get("hint_string", "")
28 | })
29 |
30 | func remove_setting(setting: Dictionary):
31 | if not ProjectSettings.has_setting(setting.name):
32 | return
33 |
34 | ProjectSettings.clear(setting.name)
35 |
--------------------------------------------------------------------------------
/materials/ball.tres:
--------------------------------------------------------------------------------
1 | [gd_resource type="StandardMaterial3D" load_steps=5 format=3 uid="uid://bp1lxtywe5u3c"]
2 |
3 | [ext_resource type="Texture2D" uid="uid://cc4mhe4g0ymsv" path="res://materials/ball_albedo.png" id="1"]
4 | [ext_resource type="Texture2D" uid="uid://dyh6m3p6ofsrk" path="res://materials/ball_orm.png" id="2"]
5 | [ext_resource type="Texture2D" uid="uid://d3u7ar71ppy7g" path="res://materials/ball_normal.png" id="3"]
6 | [ext_resource type="Texture2D" uid="uid://6y5nxpmq7pwg" path="res://materials/ball_heightmap.png" id="4"]
7 |
8 | [resource]
9 | albedo_texture = ExtResource("1")
10 | metallic = 1.0
11 | metallic_texture = ExtResource("2")
12 | metallic_texture_channel = 2
13 | roughness_texture = ExtResource("2")
14 | roughness_texture_channel = 1
15 | normal_enabled = true
16 | normal_texture = ExtResource("3")
17 | ao_enabled = true
18 | ao_texture = ExtResource("2")
19 | heightmap_enabled = true
20 | heightmap_scale = 12.5
21 | heightmap_deep_parallax = true
22 | heightmap_min_layers = 8
23 | heightmap_max_layers = 32
24 | heightmap_texture = ExtResource("4")
25 |
--------------------------------------------------------------------------------
/addons/netfox/icons/rewindable-action.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://dy3qj4jbh0enm"
6 | path="res://.godot/imported/rewindable-action.svg-2eb32b564a2fe4ecb8b77ec5b55683ab.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://addons/netfox/icons/rewindable-action.svg"
14 | dest_files=["res://.godot/imported/rewindable-action.svg-2eb32b564a2fe4ecb8b77ec5b55683ab.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 | svg/scale=1.0
36 | editor/scale_with_editor_scale=false
37 | editor/convert_colors_with_editor_theme=false
38 |
--------------------------------------------------------------------------------
/addons/netfox/icons/tick-interpolator.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://cpaqxcohxtb68"
6 | path="res://.godot/imported/tick-interpolator.svg-c60124cd7d287f516c89a6022efef330.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://addons/netfox/icons/tick-interpolator.svg"
14 | dest_files=["res://.godot/imported/tick-interpolator.svg-c60124cd7d287f516c89a6022efef330.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 | svg/scale=1.0
36 | editor/scale_with_editor_scale=false
37 | editor/convert_colors_with_editor_theme=false
38 |
--------------------------------------------------------------------------------
/addons/netfox/icons/state-synchronizer.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://ogbi1hffcoyh"
6 | path="res://.godot/imported/state-synchronizer.svg-9cb9447ba79f114a58e468a24d17b860.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://addons/netfox/icons/state-synchronizer.svg"
14 | dest_files=["res://.godot/imported/state-synchronizer.svg-9cb9447ba79f114a58e468a24d17b860.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 | svg/scale=1.0
36 | editor/scale_with_editor_scale=false
37 | editor/convert_colors_with_editor_theme=false
38 |
--------------------------------------------------------------------------------
/addons/netfox.extras/icons/rewindable-state.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://dsxkv3ufy1r2q"
6 | path="res://.godot/imported/rewindable-state.svg-b534e5a6f5de20b3fe0b63d612bd36bf.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://addons/netfox.extras/icons/rewindable-state.svg"
14 | dest_files=["res://.godot/imported/rewindable-state.svg-b534e5a6f5de20b3fe0b63d612bd36bf.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 | svg/scale=1.0
36 | editor/scale_with_editor_scale=false
37 | editor/convert_colors_with_editor_theme=false
38 |
--------------------------------------------------------------------------------
/addons/netfox/icons/rollback-synchronizer.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://csg26ysqqb4xe"
6 | path="res://.godot/imported/rollback-synchronizer.svg-99c6071e1009de5a35a481b2f486c380.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://addons/netfox/icons/rollback-synchronizer.svg"
14 | dest_files=["res://.godot/imported/rollback-synchronizer.svg-99c6071e1009de5a35a481b2f486c380.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 | svg/scale=1.0
36 | editor/scale_with_editor_scale=false
37 | editor/convert_colors_with_editor_theme=false
38 |
--------------------------------------------------------------------------------
/addons/netfox/time/network-clocks.gd:
--------------------------------------------------------------------------------
1 | extends Object
2 | class_name NetworkClocks
3 |
4 | class SystemClock:
5 | var offset: float = 0.
6 |
7 | func get_raw_time() -> float:
8 | return Time.get_unix_time_from_system()
9 |
10 | func get_time() -> float:
11 | return get_raw_time() + offset
12 |
13 | func adjust(p_offset: float) -> void:
14 | offset += p_offset
15 |
16 | func set_time(p_time: float) -> void:
17 | offset = p_time - get_raw_time()
18 |
19 | class SteppingClock:
20 | var time: float = 0.
21 | var last_step: float = get_raw_time()
22 |
23 | func get_raw_time() -> float:
24 | return Time.get_unix_time_from_system()
25 |
26 | func get_time() -> float:
27 | return time
28 |
29 | func adjust(p_offset: float) -> void:
30 | time += p_offset
31 |
32 | func set_time(p_time: float) -> void:
33 | last_step = get_raw_time()
34 | time = p_time
35 |
36 | func step(p_multiplier: float = 1.) -> void:
37 | var current_step := get_raw_time()
38 | var step_duration := current_step - last_step
39 | last_step = current_step
40 |
41 | adjust(step_duration * p_multiplier)
42 |
--------------------------------------------------------------------------------
/addons/netfox/icons/rollback-synchronizer.svg:
--------------------------------------------------------------------------------
1 |
2 |
18 |
--------------------------------------------------------------------------------
/addons/netfox.extras/icons/network-rigid-body-2d.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://bhm6yivew52yv"
6 | path="res://.godot/imported/network-rigid-body-2d.svg-c7ef7df16c4383a80b842fa966aa7aea.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://addons/netfox.extras/icons/network-rigid-body-2d.svg"
14 | dest_files=["res://.godot/imported/network-rigid-body-2d.svg-c7ef7df16c4383a80b842fa966aa7aea.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 | svg/scale=1.0
36 | editor/scale_with_editor_scale=false
37 | editor/convert_colors_with_editor_theme=false
38 |
--------------------------------------------------------------------------------
/addons/netfox.extras/icons/network-rigid-body-3d.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://b2lri7ofmq6a7"
6 | path="res://.godot/imported/network-rigid-body-3d.svg-325e3a4834ddab37f186912ac93fa449.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://addons/netfox.extras/icons/network-rigid-body-3d.svg"
14 | dest_files=["res://.godot/imported/network-rigid-body-3d.svg-325e3a4834ddab37f186912ac93fa449.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 | svg/scale=1.0
36 | editor/scale_with_editor_scale=false
37 | editor/convert_colors_with_editor_theme=false
38 |
--------------------------------------------------------------------------------
/addons/netfox/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2023 Gálffy Tamás
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7 | the Software, and to permit persons to whom the Software is furnished to do so,
8 | subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19 |
--------------------------------------------------------------------------------
/addons/netfox.extras/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2023 Gálffy Tamás
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7 | the Software, and to permit persons to whom the Software is furnished to do so,
8 | subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19 |
--------------------------------------------------------------------------------
/addons/netfox.noray/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2023 Gálffy Tamás
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7 | the Software, and to permit persons to whom the Software is furnished to do so,
8 | subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19 |
--------------------------------------------------------------------------------
/addons/netfox.extras/icons/rewindable-state-machine.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://cx8j75i6acsic"
6 | path="res://.godot/imported/rewindable-state-machine.svg-87ca202891a7e2319363a5e2f5387494.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://addons/netfox.extras/icons/rewindable-state-machine.svg"
14 | dest_files=["res://.godot/imported/rewindable-state-machine.svg-87ca202891a7e2319363a5e2f5387494.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 | svg/scale=1.0
36 | editor/scale_with_editor_scale=false
37 | editor/convert_colors_with_editor_theme=false
38 |
--------------------------------------------------------------------------------
/addons/netfox.internals/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2023 Gálffy Tamás
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7 | the Software, and to permit persons to whom the Software is furnished to do so,
8 | subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Alberto Klocker
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/addons/debug_draw_3d/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 DmitriySalnikov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the Software), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, andor sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/addons/netfox.extras/weapon/network-weapon-proxy.gd:
--------------------------------------------------------------------------------
1 | extends NetworkWeapon
2 | class_name _NetworkWeaponProxy
3 |
4 | var c_can_fire: Callable
5 | var c_can_peer_use: Callable
6 | var c_after_fire: Callable
7 | var c_spawn: Callable
8 | var c_get_data: Callable
9 | var c_apply_data: Callable
10 | var c_is_reconcilable: Callable
11 | var c_reconcile: Callable
12 |
13 | func _can_fire() -> bool:
14 | return c_can_fire.call()
15 |
16 | func _can_peer_use(peer_id: int) -> bool:
17 | return c_can_peer_use.call(peer_id)
18 |
19 | func _after_fire(projectile: Node):
20 | c_after_fire.call(projectile)
21 |
22 | func _spawn() -> Node:
23 | return c_spawn.call()
24 |
25 | func _get_data(projectile: Node) -> Dictionary:
26 | return c_get_data.call(projectile)
27 |
28 | func _apply_data(projectile: Node, data: Dictionary):
29 | c_apply_data.call(projectile, data)
30 |
31 | func _is_reconcilable(projectile: Node, request_data: Dictionary, local_data: Dictionary) -> bool:
32 | return c_is_reconcilable.call(projectile, request_data, local_data)
33 |
34 | func _reconcile(projectile: Node, local_data: Dictionary, remote_data: Dictionary):
35 | c_reconcile.call(projectile, local_data, remote_data)
36 |
--------------------------------------------------------------------------------
/addons/netfox/README.md:
--------------------------------------------------------------------------------
1 | # netfox
2 |
3 | The core addon of [netfox], providing responsive multiplayer features for the
4 | [Godot Engine].
5 |
6 | ## Features
7 |
8 | * ⏲️ Synchronized time
9 | * Runs game logic at a fixed, configurable tickrate
10 | * Time synchronized to game host
11 | * 🧈 State interpolation
12 | * Render 24fps tickrate at buttery smooth 60fps or more
13 | * Add a `TickInterpolator` node and it just works
14 | * 💨 Lag compensation with CSP
15 | * Implement responsive player motion with little to no extra code
16 | * Just use the `RollbackSynchronizer` node for state synchronization
17 |
18 | ## Install
19 |
20 | See the root [README](../../README.md).
21 |
22 | ## Usage
23 |
24 | See the [docs](https://foxssake.github.io/netfox/).
25 |
26 | ## License
27 |
28 | netfox is under the [MIT license](LICENSE).
29 |
30 | ## Issues
31 |
32 | In case of any issues, comments, or questions, please feel free to [open an issue]!
33 |
34 | [netfox]: https://github.com/foxssake/netfox
35 | [source]: https://github.com/foxssake/netfox/archive/refs/heads/main.zip
36 | [Godot engine]: https://godotengine.org/
37 | [open an issue]: https://github.com/foxssake/netfox/issues
38 |
--------------------------------------------------------------------------------
/materials/grass_albedo.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://cicithvrtycgj"
6 | path.s3tc="res://.godot/imported/grass_albedo.png-57e8badbbc8384a3f2937b01be12939e.s3tc.ctex"
7 | metadata={
8 | "imported_formats": ["s3tc_bptc"],
9 | "vram_texture": true
10 | }
11 |
12 | [deps]
13 |
14 | source_file="res://materials/grass_albedo.png"
15 | dest_files=["res://.godot/imported/grass_albedo.png-57e8badbbc8384a3f2937b01be12939e.s3tc.ctex"]
16 |
17 | [params]
18 |
19 | compress/mode=2
20 | compress/high_quality=false
21 | compress/lossy_quality=0.7
22 | compress/uastc_level=0
23 | compress/rdo_quality_loss=0.0
24 | compress/hdr_compression=1
25 | compress/normal_map=0
26 | compress/channel_pack=0
27 | mipmaps/generate=true
28 | mipmaps/limit=-1
29 | roughness/mode=0
30 | roughness/src_normal=""
31 | process/channel_remap/red=0
32 | process/channel_remap/green=1
33 | process/channel_remap/blue=2
34 | process/channel_remap/alpha=3
35 | process/fix_alpha_border=true
36 | process/premult_alpha=false
37 | process/normal_map_invert_y=false
38 | process/hdr_as_srgb=false
39 | process/hdr_clamp_exposure=false
40 | process/size_limit=0
41 | detect_3d/compress_to=0
42 |
--------------------------------------------------------------------------------
/addons/netfox/icons/state-synchronizer.svg:
--------------------------------------------------------------------------------
1 |
2 |
21 |
--------------------------------------------------------------------------------
/addons/debug_draw_3d/libs/libdd3d.macos.editor.universal.framework/Resources/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleInfoDictionaryVersion
6 | 6.0
7 | CFBundleDevelopmentRegion
8 | en
9 | CFBundleExecutable
10 | libdd3d.macos.editor.universal.dylib
11 | CFBundleName
12 | Debug Draw 3D
13 | CFBundleDisplayName
14 | Debug Draw 3D
15 | CFBundleIdentifier
16 | ru.dmitriysalnikov.dd3d
17 | NSHumanReadableCopyright
18 | Copyright (c) Dmitriy Salnikov.
19 | CFBundleVersion
20 | 1.4.5
21 | CFBundleShortVersionString
22 | 1.4.5
23 | CFBundlePackageType
24 | FMWK
25 | CSResourcesFileMapped
26 |
27 | DTPlatformName
28 | macosx
29 | LSMinimumSystemVersion
30 | 10.14
31 |
32 |
33 |
--------------------------------------------------------------------------------
/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.framework/Resources/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleInfoDictionaryVersion
6 | 6.0
7 | CFBundleDevelopmentRegion
8 | en
9 | CFBundleExecutable
10 | libdd3d.macos.template_release.universal.dylib
11 | CFBundleName
12 | Debug Draw 3D
13 | CFBundleDisplayName
14 | Debug Draw 3D
15 | CFBundleIdentifier
16 | ru.dmitriysalnikov.dd3d
17 | NSHumanReadableCopyright
18 | Copyright (c) Dmitriy Salnikov.
19 | CFBundleVersion
20 | 1.4.5
21 | CFBundleShortVersionString
22 | 1.4.5
23 | CFBundlePackageType
24 | FMWK
25 | CSResourcesFileMapped
26 |
27 | DTPlatformName
28 | macosx
29 | LSMinimumSystemVersion
30 | 10.14
31 |
32 |
33 |
--------------------------------------------------------------------------------
/addons/netfox/serializers/tickset-serializer.gd:
--------------------------------------------------------------------------------
1 | extends Object
2 | class_name _TicksetSerializer
3 |
4 | static func serialize(earliest_tick: int, latest_tick: int, active_ticks: _Set) -> PackedByteArray:
5 | var buffer := StreamPeerBuffer.new()
6 | var tickset_duration = latest_tick - earliest_tick
7 |
8 | assert(latest_tick >= earliest_tick, "Tickset ends before it starts!")
9 | assert(tickset_duration <= 255, "Tickset covers more than supported 255 ticks!")
10 |
11 | buffer.put_u32(earliest_tick)
12 | buffer.put_u8(tickset_duration)
13 |
14 | for tick in active_ticks:
15 | assert(tick <= latest_tick, "Trying to serialize ticks beyond latest !")
16 | buffer.put_u8(tick - earliest_tick)
17 |
18 | return buffer.data_array
19 |
20 | static func deserialize(bytes: PackedByteArray) -> Array:
21 | var buffer := StreamPeerBuffer.new()
22 | buffer.data_array = bytes
23 |
24 | var earliest_tick := buffer.get_u32()
25 | var tickset_duration := buffer.get_u8()
26 | var latest_tick := earliest_tick + tickset_duration
27 |
28 | var active_ticks := _Set.new()
29 |
30 | while buffer.get_available_bytes() > 0:
31 | var tick = earliest_tick + buffer.get_u8()
32 | active_ticks.add(tick)
33 |
34 | return [earliest_tick, latest_tick, active_ticks]
35 |
--------------------------------------------------------------------------------
/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.enabled.framework/Resources/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleInfoDictionaryVersion
6 | 6.0
7 | CFBundleDevelopmentRegion
8 | en
9 | CFBundleExecutable
10 | libdd3d.macos.template_release.universal.enabled.dylib
11 | CFBundleName
12 | Debug Draw 3D
13 | CFBundleDisplayName
14 | Debug Draw 3D
15 | CFBundleIdentifier
16 | ru.dmitriysalnikov.dd3d
17 | NSHumanReadableCopyright
18 | Copyright (c) Dmitriy Salnikov.
19 | CFBundleVersion
20 | 1.4.5
21 | CFBundleShortVersionString
22 | 1.4.5
23 | CFBundlePackageType
24 | FMWK
25 | CSResourcesFileMapped
26 |
27 | DTPlatformName
28 | macosx
29 | LSMinimumSystemVersion
30 | 10.14
31 |
32 |
33 |
--------------------------------------------------------------------------------
/addons/netfox/icons/tick-interpolator.svg:
--------------------------------------------------------------------------------
1 |
2 |
20 |
--------------------------------------------------------------------------------
/addons/netfox/properties/property-config.gd:
--------------------------------------------------------------------------------
1 | extends RefCounted
2 | class_name _PropertyConfig
3 |
4 | var _properties: Array[PropertyEntry] = []
5 | var _auth_properties: Dictionary = {} # Peer (int) to owned properties (Array[PropertyEntry])
6 |
7 | var local_peer_id: int
8 |
9 | func clear() -> void:
10 | _properties.clear()
11 | _auth_properties.clear()
12 |
13 | func set_properties(p_properties: Array[PropertyEntry]) -> void:
14 | clear()
15 | _properties.assign(p_properties)
16 |
17 | func set_properties_from_paths(property_paths: Array[String], property_cache: PropertyCache) -> void:
18 | clear()
19 | for path in property_paths:
20 | _properties.append(property_cache.get_entry(path))
21 |
22 | func get_properties() -> Array[PropertyEntry]:
23 | return _properties
24 |
25 | func get_owned_properties() -> Array[PropertyEntry]:
26 | return get_properties_owned_by(local_peer_id)
27 |
28 | func get_properties_owned_by(peer: int) -> Array[PropertyEntry]:
29 | if not _auth_properties.has(peer):
30 | var owned_properties: Array[PropertyEntry] = []
31 | for property_entry in _properties:
32 | if property_entry.node.get_multiplayer_authority() == peer:
33 | owned_properties.append(property_entry)
34 | _auth_properties[peer] = owned_properties
35 |
36 | return _auth_properties[peer]
37 |
--------------------------------------------------------------------------------
/addons/netfox.noray/README.md:
--------------------------------------------------------------------------------
1 | # netfox.noray
2 |
3 | Bulletproof your connectivity with [netfox]'s [noray] integration!
4 |
5 | ## Features
6 |
7 | * 🤝 Establish connectivity using NAT punchthrough
8 | * Uses [noray] for orchestration
9 | * Implements a full UDP handshake
10 | * 🛜 Use [noray] as a relay
11 | * Useful in cases where NAT punchthrough fails
12 | * If you can see this repo, you probably can connect through [noray]
13 |
14 | ## Install
15 |
16 | See the root [README](../../README.md).
17 |
18 | > *Note*, that while *netfox.noray* is part of the *netfox* suite, it can be
19 | > used alone, without installing *netfox* itself.
20 |
21 | ## Usage
22 |
23 | See the [docs](https://foxssake.github.io/netfox/netfox.noray/guides/noray/).
24 |
25 | For a full example, see [noray-bootstrapper.gd].
26 |
27 | ## License
28 |
29 | netfox.noray is under the [MIT license](LICENSE).
30 |
31 | ## Issues
32 |
33 | In case of any issues, comments, or questions, please feel free to [open an issue]!
34 |
35 | [netfox]: https://github.com/foxssake/netfox
36 | [source]: https://github.com/foxssake/netfox/archive/refs/heads/main.zip
37 | [noray]: https://github.com/foxssake/noray
38 | [noray-bootstrapper.gd]: ../../examples/shared/scripts/noray-bootstrapper.gd
39 | [open an issue]: https://github.com/foxssake/netfox/issues
40 |
--------------------------------------------------------------------------------
/addons/netfox.internals/set.gd:
--------------------------------------------------------------------------------
1 | extends RefCounted
2 | class_name _Set
3 |
4 | var _data: Dictionary = {}
5 | var _iterator_idx: int = -1
6 |
7 | static func of(items: Array) -> _Set:
8 | var result := _Set.new()
9 | for item in items:
10 | result.add(item)
11 | return result
12 |
13 | func add(value):
14 | _data[value] = true
15 |
16 | func has(value) -> bool:
17 | return _data.has(value)
18 |
19 | func size() -> int:
20 | return _data.size()
21 |
22 | func is_empty() -> bool:
23 | return _data.is_empty()
24 |
25 | func erase(value):
26 | return _data.erase(value)
27 |
28 | func clear():
29 | _data.clear()
30 |
31 | func values() -> Array:
32 | return _data.keys()
33 |
34 | func min():
35 | return _data.keys().min()
36 |
37 | func max():
38 | return _data.keys().max()
39 |
40 | func equals(other) -> bool:
41 | if not other or not other is _Set:
42 | return false
43 |
44 | return values() == other.values()
45 |
46 | func _to_string():
47 | return "Set" + str(values())
48 |
49 | func _iter_init(arg) -> bool:
50 | _iterator_idx = 0
51 | return _can_iterate()
52 |
53 | func _iter_next(arg) -> bool:
54 | _iterator_idx += 1
55 | return _can_iterate()
56 |
57 | func _iter_get(arg):
58 | return _data.keys()[_iterator_idx]
59 |
60 | func _can_iterate() -> bool:
61 | if _data.is_empty() or _iterator_idx >= _data.size():
62 | _iterator_idx = -1
63 | return false
64 | return true
65 |
--------------------------------------------------------------------------------
/addons/netfox/properties/property-entry.gd:
--------------------------------------------------------------------------------
1 | extends RefCounted
2 | class_name PropertyEntry
3 |
4 | var _path: String
5 | var node: Node
6 | var property: String
7 |
8 | static var _logger := _NetfoxLogger.for_netfox("PropertyEntry")
9 |
10 | func get_value() -> Variant:
11 | return node.get_indexed(property)
12 |
13 | func set_value(value):
14 | node.set_indexed(property, value)
15 |
16 | func is_valid() -> bool:
17 | if not node or not is_instance_valid(node):
18 | # Node is invalid
19 | return false
20 |
21 | # Return true if node has given property
22 | return node.get_property_list()\
23 | .any(func(it): return it["name"] == property)
24 |
25 | func _to_string() -> String:
26 | return _path
27 |
28 | static func parse(root: Node, path: String) -> PropertyEntry:
29 | var result = PropertyEntry.new()
30 | result.node = root.get_node(NodePath(path))
31 | result.property = path.erase(0, path.find(":") + 1)
32 | result._path = path
33 | return result
34 |
35 | static func make_path(root: Node, node: Variant, property: String) -> String:
36 | var node_path := ""
37 |
38 | if node is String:
39 | node_path = node
40 | elif node is NodePath:
41 | node_path = str(node)
42 | elif node is Node:
43 | node_path = str(root.get_path_to(node))
44 | else:
45 | _logger.error("Can't stringify node reference: %s", [node])
46 | return ""
47 |
48 | if node_path == ".":
49 | node_path = ""
50 |
51 | return "%s:%s" % [node_path, property]
52 |
--------------------------------------------------------------------------------
/addons/netfox.noray/netfox-noray.gd:
--------------------------------------------------------------------------------
1 | @tool
2 | extends EditorPlugin
3 |
4 | const ROOT = "res://addons/netfox.noray"
5 |
6 | var SETTINGS = [
7 | _NetfoxLogger.make_setting("netfox/logging/netfox_noray_log_level")
8 | ]
9 |
10 | const AUTOLOADS = [
11 | {
12 | "name": "Noray",
13 | "path": ROOT + "/noray.gd"
14 | },
15 | {
16 | "name": "PacketHandshake",
17 | "path": ROOT + "/packet-handshake.gd"
18 | }
19 | ]
20 |
21 | func _enter_tree():
22 | for setting in SETTINGS:
23 | add_setting(setting)
24 |
25 | for autoload in AUTOLOADS:
26 | add_autoload_singleton(autoload.name, autoload.path)
27 |
28 | func _exit_tree():
29 | if ProjectSettings.get_setting("netfox/general/clear_settings", false):
30 | for setting in SETTINGS:
31 | remove_setting(setting)
32 |
33 | for autoload in AUTOLOADS:
34 | remove_autoload_singleton(autoload.name)
35 |
36 | func add_setting(setting: Dictionary):
37 | if ProjectSettings.has_setting(setting.name):
38 | return
39 |
40 | ProjectSettings.set_setting(setting.name, setting.value)
41 | ProjectSettings.set_initial_value(setting.name, setting.value)
42 | ProjectSettings.add_property_info({
43 | "name": setting.get("name"),
44 | "type": setting.get("type"),
45 | "hint": setting.get("hint", PROPERTY_HINT_NONE),
46 | "hint_string": setting.get("hint_string", "")
47 | })
48 |
49 | func remove_setting(setting: Dictionary):
50 | if not ProjectSettings.has_setting(setting.name):
51 | return
52 |
53 | ProjectSettings.clear(setting.name)
54 |
--------------------------------------------------------------------------------
/addons/netfox.internals/bimap.gd:
--------------------------------------------------------------------------------
1 | extends RefCounted
2 | class_name _BiMap
3 |
4 | # Maps one-to-one associations in a bidirectional way
5 |
6 | var _keys_to_values := {}
7 | var _values_to_keys := {}
8 |
9 | func put(key: Variant, value: Variant) -> void:
10 | var old_value = _keys_to_values.get(key)
11 | var old_key = _values_to_keys.get(value)
12 |
13 | if old_value != null: _values_to_keys.erase(old_value)
14 | if old_key != null: _keys_to_values.erase(old_key)
15 |
16 | _keys_to_values[key] = value
17 | _values_to_keys[value] = key
18 |
19 | func get_by_key(key: Variant) -> Variant:
20 | return _keys_to_values.get(key)
21 |
22 | func get_by_value(value: Variant) -> Variant:
23 | return _values_to_keys.get(value)
24 |
25 | func has_key(key: Variant) -> bool:
26 | return _keys_to_values.has(key)
27 |
28 | func has_value(value: Variant) -> bool:
29 | return _values_to_keys.has(value)
30 |
31 | func erase_key(key: Variant) -> bool:
32 | if not has_key(key):
33 | return false
34 |
35 | var value = get_by_key(key)
36 | _values_to_keys.erase(value)
37 | _keys_to_values.erase(key)
38 | return true
39 |
40 | func erase_value(value: Variant) -> bool:
41 | if not has_value(value):
42 | return false
43 |
44 | var key = get_by_value(value)
45 | _values_to_keys.erase(value)
46 | _keys_to_values.erase(key)
47 | return true
48 |
49 | func size() -> int:
50 | return _keys_to_values.size()
51 |
52 | func keys() -> Array:
53 | return _keys_to_values.keys()
54 |
55 | func values() -> Array:
56 | return _values_to_keys.keys()
57 |
--------------------------------------------------------------------------------
/materials/grass.tres:
--------------------------------------------------------------------------------
1 | [gd_resource type="StandardMaterial3D" load_steps=6 format=3 uid="uid://bnqudedf7vlb0"]
2 |
3 | [ext_resource type="Texture2D" uid="uid://cicithvrtycgj" path="res://materials/grass_albedo.png" id="1"]
4 | [ext_resource type="Texture2D" uid="uid://dys4dhkhk3pw4" path="res://materials/grass_orm.png" id="2"]
5 | [ext_resource type="Texture2D" uid="uid://mr7pu5igx84m" path="res://materials/grass_normal.png" id="3"]
6 | [ext_resource type="Texture2D" uid="uid://cx8yeg0tx1e4h" path="res://materials/grass_heightmap.png" id="4"]
7 | [ext_resource type="Texture2D" uid="uid://c5xh5cxrpjwps" path="res://materials/grass_emission.png" id="5"]
8 |
9 | [resource]
10 | albedo_color = Color(0.27340657, 0.5222743, 0.27338237, 1)
11 | albedo_texture = ExtResource("1")
12 | metallic = 0.62
13 | metallic_specular = 0.24
14 | metallic_texture = ExtResource("2")
15 | metallic_texture_channel = 2
16 | roughness = 0.9
17 | roughness_texture = ExtResource("2")
18 | roughness_texture_channel = 1
19 | emission_enabled = true
20 | emission_texture = ExtResource("5")
21 | normal_enabled = true
22 | normal_texture = ExtResource("3")
23 | ao_enabled = true
24 | ao_light_affect = 0.62
25 | ao_texture = ExtResource("2")
26 | heightmap_scale = 4.402
27 | heightmap_texture = ExtResource("4")
28 | subsurf_scatter_enabled = true
29 | subsurf_scatter_strength = 0.26
30 | backlight = Color(0.368117, 0.41861, 0.25716, 1)
31 | uv1_triplanar = true
32 | uv1_triplanar_sharpness = 1.4142138
33 | texture_filter = 5
34 | proximity_fade_enabled = true
35 | distance_fade_mode = 1
36 |
--------------------------------------------------------------------------------
/models/car.glb.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="scene"
4 | importer_version=1
5 | type="PackedScene"
6 | uid="uid://dtoa53rtwixhg"
7 | path="res://.godot/imported/car.glb-6d45222c70795b114333c5c84f1bce6b.scn"
8 |
9 | [deps]
10 |
11 | source_file="res://models/car.glb"
12 | dest_files=["res://.godot/imported/car.glb-6d45222c70795b114333c5c84f1bce6b.scn"]
13 |
14 | [params]
15 |
16 | nodes/root_type=""
17 | nodes/root_name=""
18 | nodes/root_script=null
19 | nodes/apply_root_scale=true
20 | nodes/root_scale=1.0
21 | nodes/import_as_skeleton_bones=false
22 | nodes/use_name_suffixes=true
23 | nodes/use_node_type_suffixes=true
24 | meshes/ensure_tangents=true
25 | meshes/generate_lods=true
26 | meshes/create_shadow_meshes=true
27 | meshes/light_baking=1
28 | meshes/lightmap_texel_size=0.2
29 | meshes/force_disable_compression=false
30 | skins/use_named_skins=true
31 | animation/import=true
32 | animation/fps=30
33 | animation/trimming=false
34 | animation/remove_immutable_tracks=true
35 | animation/import_rest_as_RESET=false
36 | import_script/path=""
37 | materials/extract=0
38 | materials/extract_format=0
39 | materials/extract_path=""
40 | _subresources={
41 | "materials": {
42 | "Material.001": {
43 | "use_external/enabled": true,
44 | "use_external/fallback_path": "res://materials/car_paint.tres",
45 | "use_external/path": "uid://b6yeasaavorsj"
46 | },
47 | "Material.002": {
48 | "use_external/enabled": true,
49 | "use_external/fallback_path": "res://materials/car_window.tres",
50 | "use_external/path": "uid://d0ug2v7jto7a4"
51 | }
52 | }
53 | }
54 | gltf/naming_version=2
55 | gltf/embedded_image_handling=1
56 |
--------------------------------------------------------------------------------
/models/field.glb.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="scene"
4 | importer_version=1
5 | type="PackedScene"
6 | uid="uid://blu8uqh0vbrhi"
7 | path="res://.godot/imported/field.glb-af956a1e9b913de5f46376b9c4da8c26.scn"
8 |
9 | [deps]
10 |
11 | source_file="res://models/field.glb"
12 | dest_files=["res://.godot/imported/field.glb-af956a1e9b913de5f46376b9c4da8c26.scn"]
13 |
14 | [params]
15 |
16 | nodes/root_type=""
17 | nodes/root_name=""
18 | nodes/root_script=null
19 | nodes/apply_root_scale=true
20 | nodes/root_scale=1.0
21 | nodes/import_as_skeleton_bones=false
22 | nodes/use_name_suffixes=true
23 | nodes/use_node_type_suffixes=true
24 | meshes/ensure_tangents=true
25 | meshes/generate_lods=true
26 | meshes/create_shadow_meshes=true
27 | meshes/light_baking=1
28 | meshes/lightmap_texel_size=0.2
29 | meshes/force_disable_compression=false
30 | skins/use_named_skins=true
31 | animation/import=true
32 | animation/fps=30
33 | animation/trimming=false
34 | animation/remove_immutable_tracks=true
35 | animation/import_rest_as_RESET=false
36 | import_script/path=""
37 | materials/extract=0
38 | materials/extract_format=0
39 | materials/extract_path=""
40 | _subresources={
41 | "materials": {
42 | "Material.001": {
43 | "use_external/enabled": true,
44 | "use_external/fallback_path": "res://materials/wall.tres",
45 | "use_external/path": "uid://uo7itoedkenq"
46 | },
47 | "Material.002": {
48 | "use_external/enabled": true,
49 | "use_external/fallback_path": "res://materials/grass.tres",
50 | "use_external/path": "uid://bnqudedf7vlb0"
51 | }
52 | }
53 | }
54 | gltf/naming_version=2
55 | gltf/embedded_image_handling=1
56 |
--------------------------------------------------------------------------------
/addons/netfox.internals/history-buffer.gd:
--------------------------------------------------------------------------------
1 | extends RefCounted
2 | class_name _HistoryBuffer
3 |
4 | # Maps ticks (int) to arbitrary data
5 | var _buffer: Dictionary = {}
6 |
7 | func get_snapshot(tick: int):
8 | if _buffer.has(tick):
9 | return _buffer[tick]
10 | else:
11 | return null
12 |
13 | func set_snapshot(tick: int, data):
14 | _buffer[tick] = data
15 |
16 | func get_buffer() -> Dictionary:
17 | return _buffer
18 |
19 | func get_earliest_tick() -> int:
20 | return _buffer.keys().min()
21 |
22 | func get_latest_tick() -> int:
23 | return _buffer.keys().max()
24 |
25 | func get_closest_tick(tick: int) -> int:
26 | if _buffer.has(tick):
27 | return tick
28 |
29 | if _buffer.is_empty():
30 | return -1
31 |
32 | var earliest_tick = get_earliest_tick()
33 | if tick < earliest_tick:
34 | return earliest_tick
35 |
36 | var latest_tick = get_latest_tick()
37 | if tick > latest_tick:
38 | return latest_tick
39 |
40 | return _buffer.keys() \
41 | .filter(func (key): return key < tick) \
42 | .max()
43 |
44 | func get_history(tick: int):
45 | var closest_tick = get_closest_tick(tick)
46 | return _buffer.get(closest_tick)
47 |
48 | func trim(earliest_tick_to_keep: int):
49 | var ticks := _buffer.keys()
50 | for tick in ticks:
51 | if tick < earliest_tick_to_keep:
52 | _buffer.erase(tick)
53 |
54 | func clear():
55 | _buffer.clear()
56 |
57 | func size() -> int:
58 | return _buffer.size()
59 |
60 | func is_empty() -> bool:
61 | return _buffer.is_empty()
62 |
63 | func has(tick) -> bool:
64 | return _buffer.has(tick)
65 |
66 | func ticks() -> Array:
67 | return _buffer.keys()
68 |
69 | func erase(tick):
70 | _buffer.erase(tick)
71 |
--------------------------------------------------------------------------------
/addons/netfox.extras/physics/network-rigid-body-2d.gd:
--------------------------------------------------------------------------------
1 | @icon("res://addons/netfox.extras/icons/network-rigid-body-2d.svg")
2 | extends RigidBody2D
3 | class_name NetworkRigidBody2D
4 |
5 | ## A rollback / state synchronizer class for RigidBody2D.
6 | ## Set state property path to physics_state to synchronize the state of this body.
7 |
8 | @onready var direct_state = PhysicsServer2D.body_get_direct_state(get_rid())
9 |
10 | var physics_state: Array:
11 | get: return get_state()
12 | set(v): set_state(v)
13 |
14 | enum {
15 | ORIGIN,
16 | ROT,
17 | LIN_VEL,
18 | ANG_VEL,
19 | SLEEPING
20 | }
21 |
22 | func _notification(notification: int):
23 | if notification == NOTIFICATION_READY:
24 | add_to_group("network_rigid_body")
25 |
26 | func get_state() -> Array:
27 | var body_state: Array = [Vector3.ZERO, Quaternion.IDENTITY, Vector3.ZERO, Vector3.ZERO, false]
28 | body_state[ORIGIN] = direct_state.transform.origin
29 | body_state[ROT] = direct_state.transform.get_rotation()
30 | body_state[LIN_VEL] = direct_state.linear_velocity
31 | body_state[ANG_VEL] = direct_state.angular_velocity
32 | body_state[SLEEPING] = direct_state.sleeping
33 | return body_state
34 |
35 | func set_state(remote_state: Array) -> void:
36 | direct_state.transform = Transform2D(remote_state[ROT], remote_state[ORIGIN])
37 | direct_state.linear_velocity = remote_state[LIN_VEL]
38 | direct_state.angular_velocity = remote_state[ANG_VEL]
39 | direct_state.sleeping = remote_state[SLEEPING]
40 |
41 | ## Override and apply any logic, forces or impulses to the rigid body as you would in physics_process
42 | ## The physics engine will run its simulation during rollback_tick with other nodes
43 | func _physics_rollback_tick(_delta, _tick):
44 | pass
45 |
--------------------------------------------------------------------------------
/addons/netfox.internals/editor-utils.gd:
--------------------------------------------------------------------------------
1 | extends Object
2 | class_name _NetfoxEditorUtils
3 |
4 | static func gather_properties(root: Node, callback_name: String, handler: Callable) -> Array[String]:
5 | var result: Array[String] = []
6 |
7 | var nodes: Array[Node] = root.find_children("*")
8 | nodes.push_front(root)
9 | for node in nodes:
10 | if not node.has_method(callback_name):
11 | continue
12 |
13 | var readable_node_name := "\"%s\" (\"%s\")" % [node.name, root.get_path_to(node)]
14 | if node.get(callback_name) == null:
15 | result.push_back("Can't grab method \"%s\" from node %s! Is it a @tool?" % [callback_name, readable_node_name])
16 | continue
17 |
18 | var props = node.get(callback_name).call()
19 | if not props is Array:
20 | result.push_back("Node %s didn't return an array on calling \"%s\"" % [readable_node_name, callback_name])
21 | continue
22 |
23 | for prop in props:
24 | if prop is String:
25 | # Property is a string, meaning property path relative to node
26 | handler.call(node, prop)
27 | elif prop is Array and prop.size() >= 2:
28 | # Property is a node-property tuple
29 | var prop_node: Node = null
30 |
31 | # Node can be a String, NodePath, or an actual Node
32 | if prop[0] is String or prop[0] is NodePath:
33 | prop_node = node.get_node(prop[0])
34 | elif prop[0] is Node:
35 | prop_node = prop[0]
36 | else:
37 | result.push_back("Node %s specified invalid node in \"%s\": %s" % [readable_node_name, callback_name, prop])
38 | continue
39 |
40 | handler.call(prop_node, prop[1])
41 | else:
42 | result.push_back("Node %s specified invalid property in \"%s\": %s" % [readable_node_name, callback_name, prop])
43 |
44 | return result
45 |
--------------------------------------------------------------------------------
/scripts/main.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 |
3 | #const player_scene = preload("res://scenes/player.tscn")
4 | const player_scene = preload("res://scenes/player_rb.tscn")
5 | const ADDRESS = "localhost"
6 | const PORT = 9999
7 | var enet_peer = ENetMultiplayerPeer.new()
8 |
9 | func _ready():
10 | if OS.has_feature("editor"):
11 | var status = _host()
12 | if status == Error.ERR_CANT_CREATE:
13 | _join()
14 |
15 | func _host():
16 | var status = enet_peer.create_server(PORT)
17 | if status > 0:
18 | return status
19 |
20 | enet_peer.host.compress(ENetConnection.COMPRESS_RANGE_CODER)
21 |
22 | multiplayer.multiplayer_peer = enet_peer
23 | multiplayer.peer_connected.connect(client_connected)
24 | multiplayer.peer_disconnected.connect(remove_player)
25 | add_player(multiplayer.get_unique_id())
26 |
27 | return status
28 |
29 | func client_connected(peer_id):
30 | print("Client connected: ", peer_id)
31 | add_player(peer_id)
32 |
33 | func _join():
34 | enet_peer.create_client(ADDRESS, PORT)
35 | enet_peer.host.compress(ENetConnection.COMPRESS_RANGE_CODER)
36 |
37 | multiplayer.multiplayer_peer = enet_peer
38 | multiplayer.connected_to_server.connect(connected)
39 |
40 | func _unhandled_input(_event):
41 | if Input.is_action_just_pressed("quit"):
42 | get_tree().quit()
43 |
44 | func add_player(peer_id):
45 | var player = player_scene.instantiate()
46 | player.name = str(peer_id)
47 | $Players.add_child(player)
48 | player.team = multiplayer.get_peers().size() % 2
49 |
50 |
51 | func connected():
52 | print(multiplayer.get_unique_id(), " Connected to server")
53 | await NetworkTime.after_sync
54 |
55 | func remove_player(peer_id):
56 | var player = get_node_or_null(str(peer_id))
57 | if player:
58 | player.queue_free()
59 |
--------------------------------------------------------------------------------
/addons/netfox.extras/physics/network-rigid-body-3d.gd:
--------------------------------------------------------------------------------
1 | @icon("res://addons/netfox.extras/physics/network-rigid-body-3d.gd")
2 | extends RigidBody3D
3 | class_name NetworkRigidBody3D
4 |
5 | ## A rollback / state synchronizer class for RigidBody3D.
6 | ## Set state property path to physics_state to synchronize the state of this body.
7 |
8 | @onready var direct_state = PhysicsServer3D.body_get_direct_state(get_rid())
9 |
10 | var physics_state: Array:
11 | get: return get_state()
12 | set(v): set_state(v)
13 |
14 | enum {
15 | ORIGIN,
16 | QUAT,
17 | LIN_VEL,
18 | ANG_VEL,
19 | SLEEPING
20 | }
21 |
22 | func _notification(notification: int):
23 | if notification == NOTIFICATION_READY:
24 | add_to_group("network_rigid_body")
25 |
26 | func get_state() -> Array:
27 | var body_state: Array = [Vector3.ZERO, Quaternion.IDENTITY, Vector3.ZERO, Vector3.ZERO, false]
28 | body_state[ORIGIN] = direct_state.transform.origin
29 | body_state[QUAT] = direct_state.transform.basis.get_rotation_quaternion()
30 | body_state[LIN_VEL] = direct_state.linear_velocity
31 | body_state[ANG_VEL] = direct_state.angular_velocity
32 | body_state[SLEEPING] = direct_state.sleeping
33 | return body_state
34 |
35 | func set_state(remote_state: Array) -> void:
36 | direct_state.transform.origin = remote_state[ORIGIN]
37 | direct_state.transform.basis = Basis(remote_state[QUAT])
38 | direct_state.linear_velocity = remote_state[LIN_VEL]
39 | direct_state.angular_velocity = remote_state[ANG_VEL]
40 | direct_state.sleeping = remote_state[SLEEPING]
41 |
42 |
43 | ## Override and apply any logic, forces or impulses to the rigid body as you would in physics_process
44 | ## The physics engine will run its simulation during rollback_tick with other nodes
45 | func _physics_rollback_tick(_delta, _tick):
46 | pass
47 |
--------------------------------------------------------------------------------
/models/car.blend.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="scene"
4 | importer_version=1
5 | type="PackedScene"
6 | uid="uid://mwlmhv68da6e"
7 | path="res://.godot/imported/car.blend-085967aaf200b38a9dad83df45cec0ff.scn"
8 |
9 | [deps]
10 |
11 | source_file="res://models/car.blend"
12 | dest_files=["res://.godot/imported/car.blend-085967aaf200b38a9dad83df45cec0ff.scn"]
13 |
14 | [params]
15 |
16 | nodes/root_type=""
17 | nodes/root_name=""
18 | nodes/root_script=null
19 | nodes/apply_root_scale=true
20 | nodes/root_scale=1.0
21 | nodes/import_as_skeleton_bones=false
22 | nodes/use_name_suffixes=true
23 | nodes/use_node_type_suffixes=true
24 | meshes/ensure_tangents=true
25 | meshes/generate_lods=true
26 | meshes/create_shadow_meshes=true
27 | meshes/light_baking=1
28 | meshes/lightmap_texel_size=0.2
29 | meshes/force_disable_compression=false
30 | skins/use_named_skins=true
31 | animation/import=true
32 | animation/fps=30
33 | animation/trimming=false
34 | animation/remove_immutable_tracks=true
35 | animation/import_rest_as_RESET=false
36 | import_script/path=""
37 | materials/extract=0
38 | materials/extract_format=0
39 | materials/extract_path=""
40 | _subresources={}
41 | blender/nodes/visible=0
42 | blender/nodes/active_collection_only=false
43 | blender/nodes/punctual_lights=true
44 | blender/nodes/cameras=true
45 | blender/nodes/custom_properties=true
46 | blender/nodes/modifiers=1
47 | blender/meshes/colors=false
48 | blender/meshes/uvs=true
49 | blender/meshes/normals=true
50 | blender/meshes/export_geometry_nodes_instances=false
51 | blender/meshes/tangents=true
52 | blender/meshes/skins=2
53 | blender/meshes/export_bones_deforming_mesh_only=false
54 | blender/materials/unpack_enabled=true
55 | blender/materials/export_materials=1
56 | blender/animation/limit_playback=true
57 | blender/animation/always_sample=true
58 | blender/animation/group_tracks=true
59 | gltf/naming_version=0
60 |
--------------------------------------------------------------------------------
/models/field.blend.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="scene"
4 | importer_version=1
5 | type="PackedScene"
6 | uid="uid://th8ke8d8f3dl"
7 | path="res://.godot/imported/field.blend-670b00f385322c26e0048e9d940b2503.scn"
8 |
9 | [deps]
10 |
11 | source_file="res://models/field.blend"
12 | dest_files=["res://.godot/imported/field.blend-670b00f385322c26e0048e9d940b2503.scn"]
13 |
14 | [params]
15 |
16 | nodes/root_type=""
17 | nodes/root_name=""
18 | nodes/root_script=null
19 | nodes/apply_root_scale=true
20 | nodes/root_scale=1.0
21 | nodes/import_as_skeleton_bones=false
22 | nodes/use_name_suffixes=true
23 | nodes/use_node_type_suffixes=true
24 | meshes/ensure_tangents=true
25 | meshes/generate_lods=true
26 | meshes/create_shadow_meshes=true
27 | meshes/light_baking=1
28 | meshes/lightmap_texel_size=0.2
29 | meshes/force_disable_compression=false
30 | skins/use_named_skins=true
31 | animation/import=true
32 | animation/fps=30
33 | animation/trimming=false
34 | animation/remove_immutable_tracks=true
35 | animation/import_rest_as_RESET=false
36 | import_script/path=""
37 | materials/extract=0
38 | materials/extract_format=0
39 | materials/extract_path=""
40 | _subresources={}
41 | blender/nodes/visible=0
42 | blender/nodes/active_collection_only=false
43 | blender/nodes/punctual_lights=true
44 | blender/nodes/cameras=true
45 | blender/nodes/custom_properties=true
46 | blender/nodes/modifiers=1
47 | blender/meshes/colors=false
48 | blender/meshes/uvs=true
49 | blender/meshes/normals=true
50 | blender/meshes/export_geometry_nodes_instances=false
51 | blender/meshes/tangents=true
52 | blender/meshes/skins=2
53 | blender/meshes/export_bones_deforming_mesh_only=false
54 | blender/materials/unpack_enabled=true
55 | blender/materials/export_materials=1
56 | blender/animation/limit_playback=true
57 | blender/animation/always_sample=true
58 | blender/animation/group_tracks=true
59 | gltf/naming_version=0
60 |
--------------------------------------------------------------------------------
/scenes/ball.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=8 format=3 uid="uid://dpm8cgj63eh0s"]
2 |
3 | [ext_resource type="Script" uid="uid://c0s6il6j1o06a" path="res://scripts/ball.gd" id="1_cxlvu"]
4 | [ext_resource type="Material" uid="uid://bp1lxtywe5u3c" path="res://materials/ball.tres" id="3_4dx81"]
5 | [ext_resource type="Script" uid="uid://dour8fehaaugp" path="res://addons/netfox/tick-interpolator.gd" id="3_f7cbr"]
6 | [ext_resource type="Script" uid="uid://d350u8evihs1u" path="res://addons/netfox/rollback/rollback-synchronizer.gd" id="4_4dx81"]
7 |
8 | [sub_resource type="PhysicsMaterial" id="PhysicsMaterial_0xm2m"]
9 | friction = 0.7
10 | bounce = 0.7
11 |
12 | [sub_resource type="SphereShape3D" id="SphereShape3D_0xm2m"]
13 | margin = 0.1
14 | radius = 1.0
15 |
16 | [sub_resource type="SphereMesh" id="SphereMesh_0xm2m"]
17 | radius = 1.0
18 | height = 2.0
19 |
20 | [node name="Ball" type="RigidBody3D"]
21 | collision_layer = 4
22 | collision_mask = 3
23 | physics_material_override = SubResource("PhysicsMaterial_0xm2m")
24 | script = ExtResource("1_cxlvu")
25 |
26 | [node name="CollisionShape3D" type="CollisionShape3D" parent="."]
27 | shape = SubResource("SphereShape3D_0xm2m")
28 |
29 | [node name="MeshInstance3D" type="MeshInstance3D" parent="."]
30 | mesh = SubResource("SphereMesh_0xm2m")
31 | surface_material_override/0 = ExtResource("3_4dx81")
32 |
33 | [node name="TickInterpolator" type="Node" parent="." node_paths=PackedStringArray("root")]
34 | script = ExtResource("3_f7cbr")
35 | root = NodePath("..")
36 | properties = Array[String]([":global_position"])
37 | metadata/_custom_type_script = "uid://dour8fehaaugp"
38 |
39 | [node name="RollbackSynchronizer" type="Node" parent="." node_paths=PackedStringArray("root")]
40 | script = ExtResource("4_4dx81")
41 | root = NodePath("..")
42 | state_properties = Array[String]([":physics_state", ":entered_goal"])
43 | metadata/_custom_type_script = "uid://d350u8evihs1u"
44 |
--------------------------------------------------------------------------------
/scripts/ball.gd:
--------------------------------------------------------------------------------
1 | extends NetworkRigidBody3D
2 |
3 | class_name Ball
4 |
5 | signal goal_scored(team: int)
6 |
7 | var STARTING_POSITION: Vector3 = Vector3(6.0, 8.0, 0)
8 |
9 | var confetti: PackedScene = preload("res://scenes/confetti.tscn")
10 |
11 | var entered_goal: bool = false
12 | var announced_goal: bool = false
13 | var entered_goal_tick: int = 0
14 | var reset_tick : int = 0
15 | var goal_team: int = 0
16 |
17 |
18 | func _ready():
19 | set_multiplayer_authority(1, true)
20 | contact_monitor = true
21 | max_contacts_reported = 8
22 | NetworkTime.on_tick.connect(on_tick)
23 | $RollbackSynchronizer.process_settings()
24 |
25 | func is_confirmed_tick(tick: int) -> bool:
26 | var players = get_tree().get_nodes_in_group("players")
27 | for player in players:
28 | if tick > NetworkRollback.get_latest_input_tick(player):
29 | return false
30 | return true
31 |
32 |
33 | func _physics_rollback_tick(_delta: float, tick: int) -> void:
34 | if tick == reset_tick:
35 | reset()
36 | return
37 |
38 | func entered_goal_area(area: Area3D, tick: int) -> void:
39 | entered_goal = true
40 | entered_goal_tick = tick
41 | goal_team = int(area.name.right(1))
42 |
43 | func on_tick(_delta: float, tick: int) -> void:
44 |
45 | if entered_goal and not announced_goal:
46 | # Check that no player actions can alter the outcome
47 | if is_confirmed_tick(entered_goal_tick):
48 |
49 | goal_scored.emit(goal_team)
50 | entered_goal = false
51 | announced_goal = true
52 |
53 | # Spawn confetti and hide the ball
54 | var confetti_instance = confetti.instantiate()
55 | get_tree().root.add_child(confetti_instance)
56 | confetti_instance.global_position = global_position
57 | hide()
58 |
59 | reset_tick = tick + NetworkTime.seconds_to_ticks(5.)
60 |
61 |
62 | func reset() -> void:
63 | show()
64 | entered_goal = false
65 | announced_goal = false
66 | direct_state.transform.origin = STARTING_POSITION
67 | direct_state.linear_velocity = Vector3.ZERO
68 | direct_state.angular_velocity = Vector3.ZERO
69 |
--------------------------------------------------------------------------------
/scripts/ui_info.gd:
--------------------------------------------------------------------------------
1 | extends Label
2 |
3 | var game : Game = null
4 |
5 | func _ready():
6 | NetworkTime.on_tick.connect(_tick)
7 | game = get_node("/root/World/Game")
8 |
9 |
10 | var data_sent_rate = 0.0
11 | var data_received_rate = 0.0
12 | var current_time = 0.0
13 | var last_time = 0.0
14 |
15 | func perf(type: String):
16 | match type:
17 | "fps":
18 | return Performance.get_monitor(Performance.TIME_FPS)
19 | "process":
20 | return Performance.get_monitor(Performance.TIME_PROCESS)
21 | "draw_calls":
22 | return Performance.get_monitor(Performance.RENDER_TOTAL_DRAW_CALLS_IN_FRAME)
23 |
24 | func _tick(_delta: float, _t: int):
25 | var type = "Server"
26 | if not multiplayer.is_server():
27 | type = "Client"
28 |
29 | text = "%s - %s" % [type, multiplayer.get_unique_id()]
30 | text += "\nFPS: %s " % perf("fps")
31 | text += "\ntick: %s " % NetworkTime.tick
32 |
33 | if not multiplayer.is_server():
34 | # Grab latency to server and display
35 | var enet = multiplayer.multiplayer_peer as ENetMultiplayerPeer
36 | if enet == null:
37 | return
38 |
39 | var server = enet.get_peer(1)
40 | var _mean_rtt = server.get_statistic(ENetPacketPeer.PEER_ROUND_TRIP_TIME)
41 |
42 | current_time = Time.get_ticks_msec() / 1000.0
43 |
44 | if int(current_time) > int(last_time):
45 | data_sent_rate = enet.get_host().pop_statistic(ENetConnection.HostStatistic.HOST_TOTAL_SENT_DATA)
46 | data_received_rate = enet.get_host().pop_statistic(ENetConnection.HostStatistic.HOST_TOTAL_RECEIVED_DATA)
47 | last_time = current_time
48 |
49 | text += "\nRTT: %s " % _mean_rtt
50 | text += "\nData - up: %s Kbps | down: %s Kbps" % [int(data_sent_rate * 0.008), int(data_received_rate * 0.008)]
51 |
52 | # # View input submission status
53 | # var players = get_tree().get_nodes_in_group("players")
54 | # var submissions = NetworkRollback.get_input_submissions()
55 | # for player in players:
56 | # text += "\n%s - last input: %s - %s" % [player.name, submissions.get(player), NetworkRollback.has_input_for_tick(player, NetworkRollback.tick)]
57 |
--------------------------------------------------------------------------------
/addons/netfox.extras/icons/network-rigid-body-2d.svg:
--------------------------------------------------------------------------------
1 |
2 |
53 |
--------------------------------------------------------------------------------
/addons/netfox.extras/icons/network-rigid-body-3d.svg:
--------------------------------------------------------------------------------
1 |
2 |
53 |
--------------------------------------------------------------------------------
/scripts/car_ai.gd:
--------------------------------------------------------------------------------
1 | extends Node3D
2 |
3 | @onready var inputs: PlayerInput = get_parent().get_node("Input")
4 | @onready var ball: Node3D = get_tree().get_root().get_node("World/Ball")
5 |
6 | # Turning circle compensation
7 | const MIN_DISTANCE_FROM_BALL: float = 30.0
8 | const ALIGNMENT_THRESHOLD: float = 0.5
9 |
10 | # Stuff to get unstuck
11 | const STUCK_THRESHOLD: float = 0.2
12 | const STUCK_TIME: float = 4.0
13 | const REVERSE_DURATION: float = 0.5 #
14 | var last_position: Vector3 = Vector3()
15 | var stuck_timer: float = 0.0
16 | var reverse_timer: float = 0.0
17 |
18 | func _ready() -> void:
19 | inputs.ai_enabled = true
20 | NetworkTime.on_tick.connect(_on_tick)
21 |
22 |
23 | func _input(event: InputEvent) -> void:
24 | # Disable AI if the player presses any key
25 | if event is InputEventKey and event.pressed:
26 | inputs.ai_enabled = false
27 |
28 |
29 | func _on_tick(_delta: float, _tick: int) -> void:
30 | if not ball:
31 | return
32 |
33 | if stuck_check(_delta):
34 | return
35 |
36 | # Calculate direction and distance to the ball
37 | var car_direction = global_transform.basis.z.normalized()
38 | var direction_to_ball = (ball.global_position - global_position).normalized()
39 | var cross = car_direction.cross(direction_to_ball).y
40 |
41 | var steering = 0
42 | if cross > 0.1:
43 | steering = -1 # Steer right
44 | elif cross < -0.1:
45 | steering = 1 # Steer left
46 |
47 | inputs.ai_motion = Vector2(steering, 1)
48 |
49 | func stuck_check(_delta) -> bool:
50 | # Check if the car is stuck
51 | var distance_moved = global_position.distance_to(last_position)
52 | if distance_moved < STUCK_THRESHOLD:
53 | stuck_timer += _delta
54 | else:
55 | stuck_timer = 0.0 # Reset the timer if were moving
56 |
57 | last_position = global_position
58 |
59 | # reverse if stuck
60 | if stuck_timer >= STUCK_TIME:
61 | reverse_timer = REVERSE_DURATION
62 | stuck_timer = min(stuck_timer, STUCK_TIME)
63 |
64 | inputs.ai_jumping = false
65 | if reverse_timer > 0.0:
66 | reverse_timer -= _delta
67 | inputs.ai_motion = Vector2(0, -1) # Reverse out
68 | inputs.ai_jumping = true # Jump as well, why not.
69 | return true
70 |
71 | return false
72 |
--------------------------------------------------------------------------------
/scripts/game.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 |
3 | class_name Game
4 |
5 | var kicking_off: bool = false
6 | var kickoff_tick: int = 0
7 | var kickoff_positions = {}
8 |
9 | var scores = [0, 0]
10 |
11 | @onready var score_board : RichTextLabel = $"../TopCentre/ScoreBoard"
12 | @onready var count_down: RichTextLabel = $"../TopCentre/CountDown"
13 | @onready var ball = $"../Ball"
14 | @onready var markers = $"../Markers"
15 |
16 | func _ready() -> void:
17 | ball.goal_scored.connect(on_goal_scored)
18 | NetworkTime.after_tick_loop.connect(update_scoreboard)
19 | NetworkTime.on_tick.connect(on_tick)
20 | multiplayer.peer_connected.connect(player_joined)
21 | update_scoreboard()
22 | queue_kickoff()
23 |
24 | func on_goal_scored(team: int) -> void:
25 | scores[team] += 1
26 | queue_kickoff()
27 |
28 | func on_tick(_delta: float, tick: int) -> void:
29 | if kicking_off and tick >= kickoff_tick:
30 | kicking_off = false
31 |
32 | func update_scoreboard() -> void:
33 |
34 | score_board.text = "[outline_size=5][outline_color=black][color=red]%d[/color] - [color=blue]%d[/color][/outline_color][/outline_size]" % [scores[0], scores[1]]
35 |
36 | count_down.text = ""
37 | if kicking_off:
38 | count_down.text = "[outline_size=10][outline_color=black]%d[/outline_color][/outline_size]" % max(0, NetworkTime.ticks_to_seconds(kickoff_tick - NetworkTime.tick))
39 |
40 | func queue_kickoff() -> void:
41 | if not multiplayer.is_server():
42 | return
43 |
44 | await get_tree().create_timer(3.0).timeout
45 | kicking_off = true
46 | kickoff_tick = NetworkTime.tick + NetworkTime.seconds_to_ticks(3.5)
47 | assign_kickoff_positions()
48 |
49 | func player_joined(_peer : int) -> void:
50 | await get_tree().create_timer(1.0).timeout
51 | assign_kickoff_positions()
52 |
53 | func assign_kickoff_positions() -> void:
54 |
55 | if not multiplayer.is_server():
56 | return
57 |
58 | var players = get_tree().get_nodes_in_group("players")
59 |
60 | kickoff_positions.clear()
61 | var red_index = 1
62 | var blue_index = 1
63 | for player in players:
64 | if player.team == 0:
65 | kickoff_positions[player.name] = markers.get_node("r%d" % red_index).global_position
66 | red_index += 1
67 | else:
68 | kickoff_positions[player.name] = markers.get_node("b%d" % blue_index).global_position
69 | blue_index += 1
70 |
71 |
72 |
--------------------------------------------------------------------------------
/addons/netfox/encoder/snapshot-history-encoder.gd:
--------------------------------------------------------------------------------
1 | extends RefCounted
2 | class_name _SnapshotHistoryEncoder
3 |
4 | var _history: _PropertyHistoryBuffer
5 | var _property_cache: PropertyCache
6 | var _properties: Array[PropertyEntry]
7 |
8 | var _version := -1
9 | var _has_received := false
10 |
11 | static var _logger := _NetfoxLogger.for_netfox("_SnapshotHistoryEncoder")
12 |
13 | func _init(p_history: _PropertyHistoryBuffer, p_property_cache: PropertyCache):
14 | _history = p_history
15 | _property_cache = p_property_cache
16 |
17 | func set_properties(properties: Array[PropertyEntry]) -> void:
18 | if _properties != properties:
19 | _version = (_version + 1) % 256
20 | _properties = properties.duplicate()
21 |
22 | func encode(tick: int, properties: Array[PropertyEntry]) -> Array:
23 | var snapshot := _history.get_snapshot(tick)
24 | var data := []
25 | data.resize(properties.size())
26 |
27 | for i in range(properties.size()):
28 | data[i] = snapshot.get_value(properties[i].to_string())
29 | data.append(_version)
30 |
31 | return data
32 |
33 | func decode(data: Array, properties: Array[PropertyEntry]) -> _PropertySnapshot:
34 | var result := _PropertySnapshot.new()
35 | var packet_version = data.pop_back()
36 |
37 | if packet_version != _version:
38 | if not _has_received:
39 | # First packet, assume version is OK
40 | _version = packet_version
41 | else:
42 | # Version mismatch, can't parse
43 | _logger.warning("Version mismatch! own: %d, received: %s", [_version, packet_version])
44 | return result
45 |
46 | if properties.size() != data.size():
47 | _logger.warning("Received snapshot with %d entries, with %d known - parsing as much as possible", [data.size(), properties.size()])
48 |
49 | for i in range(0, mini(data.size(), properties.size())):
50 | result.set_value(properties[i].to_string(), data[i])
51 |
52 | _has_received = true
53 |
54 | return result
55 |
56 | func apply(tick: int, snapshot: _PropertySnapshot, sender: int = -1) -> bool:
57 | if tick < NetworkRollback.history_start:
58 | # State too old!
59 | _logger.error("Received full snapshot for %s, rejecting because older than %s frames", [tick, NetworkRollback.history_limit])
60 | return false
61 |
62 | if sender > 0:
63 | snapshot.sanitize(sender, _property_cache)
64 | if snapshot.is_empty(): return false
65 |
66 | _history.set_snapshot(tick, snapshot)
67 | return true
68 |
--------------------------------------------------------------------------------
/scenes/player.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=9 format=3 uid="uid://stkxyqefx03l"]
2 |
3 | [ext_resource type="Script" uid="uid://cgwxygf25ypdb" path="res://scripts/player.gd" id="1_benb6"]
4 | [ext_resource type="Script" uid="uid://d350u8evihs1u" path="res://addons/netfox/rollback/rollback-synchronizer.gd" id="2_5abps"]
5 | [ext_resource type="Script" uid="uid://cbmc5r8tb2ub7" path="res://scripts/input.gd" id="3_bokmb"]
6 | [ext_resource type="Script" uid="uid://dour8fehaaugp" path="res://addons/netfox/tick-interpolator.gd" id="4_e5kxk"]
7 |
8 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_gr2wk"]
9 | albedo_color = Color(0.189389, 0.212656, 0.78031, 1)
10 |
11 | [sub_resource type="BoxMesh" id="BoxMesh_nrvd0"]
12 | material = SubResource("StandardMaterial3D_gr2wk")
13 | size = Vector3(0.5, 0.5, 1)
14 |
15 | [sub_resource type="PrismMesh" id="PrismMesh_dqkch"]
16 | size = Vector3(0.5, 0.5, 0.1)
17 |
18 | [sub_resource type="BoxShape3D" id="BoxShape3D_dqkch"]
19 | size = Vector3(1, 0.456177, 1)
20 |
21 | [node name="Player" type="CharacterBody3D"]
22 | collision_layer = 3
23 | collision_mask = 3
24 | script = ExtResource("1_benb6")
25 |
26 | [node name="MeshInstance3D" type="MeshInstance3D" parent="."]
27 | mesh = SubResource("BoxMesh_nrvd0")
28 |
29 | [node name="MeshInstance3D2" type="MeshInstance3D" parent="."]
30 | transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 0, 0.265293, 0)
31 | mesh = SubResource("PrismMesh_dqkch")
32 |
33 | [node name="CollisionShape3D" type="CollisionShape3D" parent="."]
34 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.0264282, 0)
35 | shape = SubResource("BoxShape3D_dqkch")
36 |
37 | [node name="RollbackSynchronizer" type="Node" parent="." node_paths=PackedStringArray("root")]
38 | script = ExtResource("2_5abps")
39 | root = NodePath("..")
40 | state_properties = Array[String]([":global_transform", ":velocity"])
41 | input_properties = Array[String](["Input:motion"])
42 |
43 | [node name="Input" type="Node" parent="."]
44 | script = ExtResource("3_bokmb")
45 |
46 | [node name="TickInterpolator" type="Node" parent="." node_paths=PackedStringArray("root")]
47 | script = ExtResource("4_e5kxk")
48 | root = NodePath("..")
49 | properties = Array[String]([":global_transform"])
50 |
51 | [node name="Label3D" type="Label3D" parent="."]
52 | visible = false
53 | billboard = 1
54 | shaded = true
55 | no_depth_test = true
56 | text = "Test
57 | "
58 | font_size = 60
59 |
--------------------------------------------------------------------------------
/addons/netfox.extras/weapon/network-weapon-2d.gd:
--------------------------------------------------------------------------------
1 | extends Node2D
2 | class_name NetworkWeapon2D
3 |
4 | ## A 2D-specific implementation of [NetworkWeapon].
5 |
6 | ## Distance to consider too large during reconciliation checks.
7 | @export var distance_threshold: float = 1.0
8 |
9 | var _weapon: _NetworkWeaponProxy
10 |
11 | func can_fire() -> bool:
12 | return _weapon.can_fire()
13 |
14 | func fire() -> Node2D:
15 | return _weapon.fire()
16 |
17 | func get_fired_tick() -> int:
18 | return _weapon.get_fired_tick()
19 |
20 | func _init():
21 | _weapon = _NetworkWeaponProxy.new()
22 | add_child(_weapon, true, INTERNAL_MODE_BACK)
23 | _weapon.owner = self
24 |
25 | _weapon.c_can_fire = _can_fire
26 | _weapon.c_can_peer_use = _can_peer_use
27 | _weapon.c_after_fire = _after_fire
28 | _weapon.c_spawn = _spawn
29 | _weapon.c_get_data = _get_data
30 | _weapon.c_apply_data = _apply_data
31 | _weapon.c_is_reconcilable = _is_reconcilable
32 | _weapon.c_reconcile = _reconcile
33 |
34 |
35 | ## See [NetworkWeapon]
36 | func _can_fire() -> bool:
37 | return false
38 |
39 | ## See [NetworkWeapon]
40 | func _can_peer_use(peer_id: int) -> bool:
41 | return true
42 |
43 | ## See [NetworkWeapon]
44 | func _after_fire(projectile: Node2D):
45 | pass
46 |
47 | ## See [NetworkWeapon]
48 | func _spawn() -> Node2D:
49 | return null
50 |
51 | func _get_data(projectile: Node2D) -> Dictionary:
52 | return {
53 | "global_transform": projectile.global_transform
54 | }
55 |
56 | func _apply_data(projectile: Node2D, data: Dictionary):
57 | projectile.global_transform = data["global_transform"]
58 |
59 | func _is_reconcilable(projectile: Node2D, request_data: Dictionary, local_data: Dictionary) -> bool:
60 | var req_transform = request_data["global_transform"] as Transform2D
61 | var loc_transform = local_data["global_transform"] as Transform2D
62 |
63 | var request_pos = req_transform.origin
64 | var local_pos = loc_transform.origin
65 |
66 | return request_pos.distance_to(local_pos) < distance_threshold
67 |
68 | func _reconcile(projectile: Node2D, local_data: Dictionary, remote_data: Dictionary):
69 | var local_transform = local_data["global_transform"] as Transform2D
70 | var remote_transform = remote_data["global_transform"] as Transform2D
71 |
72 | var relative_transform = projectile.global_transform * local_transform.inverse()
73 | var final_transform = remote_transform * relative_transform
74 |
75 | projectile.global_transform = final_transform
76 |
--------------------------------------------------------------------------------
/addons/netfox.extras/weapon/network-weapon-3d.gd:
--------------------------------------------------------------------------------
1 | extends Node3D
2 | class_name NetworkWeapon3D
3 |
4 | ## A 3D-specific implementation of [NetworkWeapon].
5 |
6 | ## Distance to consider too large during reconciliation checks.
7 | @export var distance_threshold: float = 1.0
8 |
9 | var _weapon: _NetworkWeaponProxy
10 |
11 | func can_fire() -> bool:
12 | return _weapon.can_fire()
13 |
14 | func fire() -> Node3D:
15 | return _weapon.fire()
16 |
17 | func get_fired_tick() -> int:
18 | return _weapon.get_fired_tick()
19 |
20 | func _init():
21 | _weapon = _NetworkWeaponProxy.new()
22 | add_child(_weapon, true, INTERNAL_MODE_BACK)
23 | _weapon.owner = self
24 |
25 | _weapon.c_can_fire = _can_fire
26 | _weapon.c_can_peer_use = _can_peer_use
27 | _weapon.c_after_fire = _after_fire
28 | _weapon.c_spawn = _spawn
29 | _weapon.c_get_data = _get_data
30 | _weapon.c_apply_data = _apply_data
31 | _weapon.c_is_reconcilable = _is_reconcilable
32 | _weapon.c_reconcile = _reconcile
33 |
34 |
35 | ## See [NetworkWeapon]
36 | func _can_fire() -> bool:
37 | return false
38 |
39 | ## See [NetworkWeapon]
40 | func _can_peer_use(peer_id: int) -> bool:
41 | return true
42 |
43 | ## See [NetworkWeapon]
44 | func _after_fire(projectile: Node3D):
45 | pass
46 |
47 | ## See [NetworkWeapon]
48 | func _spawn() -> Node3D:
49 | return null
50 |
51 | func _get_data(projectile: Node3D) -> Dictionary:
52 | return {
53 | "global_transform": projectile.global_transform
54 | }
55 |
56 | func _apply_data(projectile: Node3D, data: Dictionary):
57 | projectile.global_transform = data["global_transform"]
58 |
59 | func _is_reconcilable(projectile: Node3D, request_data: Dictionary, local_data: Dictionary) -> bool:
60 | var req_transform = request_data["global_transform"] as Transform3D
61 | var loc_transform = local_data["global_transform"] as Transform3D
62 |
63 | var request_pos = req_transform.origin
64 | var local_pos = loc_transform.origin
65 |
66 | return request_pos.distance_to(local_pos) < distance_threshold
67 |
68 | func _reconcile(projectile: Node3D, local_data: Dictionary, remote_data: Dictionary):
69 | var local_transform = local_data["global_transform"] as Transform3D
70 | var remote_transform = remote_data["global_transform"] as Transform3D
71 |
72 | var relative_transform = projectile.global_transform * local_transform.inverse()
73 | var final_transform = remote_transform * relative_transform
74 |
75 | projectile.global_transform = final_transform
76 |
--------------------------------------------------------------------------------
/addons/netfox.extras/rewindable-random-number-generator.gd:
--------------------------------------------------------------------------------
1 | extends RefCounted
2 | class_name RewindableRandomNumberGenerator
3 |
4 | ## Provides methods for generating pseudo-random numbers in the rollback tick
5 | ## loop.
6 | ##
7 | ## Using a regular [RandomNumberGenerator] in [code]_rollback_tick()[/code]
8 | ## would generate different numbers on each peer. It also generates different
9 | ## numbers when resimulating the same tick.
10 | ## [br][br]
11 | ## This class solves all of the above, making it suitable for use during
12 | ## rollback.
13 | ## [br][br]
14 | ## The seed must be provided on instantiation, and must be the same on all peers
15 | ## for the random number generator to work properly.
16 | ##
17 | ## @tutorial(RewindableRandomNumberGenerator Guide): https://foxssake.github.io/netfox/latest/netfox.extras/guides/rewindable-random-number-generator/
18 |
19 | var _rng: RandomNumberGenerator
20 | var _last_reset_tick := -1
21 | var _last_reset_rollback_tick := -1
22 |
23 | static var _logger := _NetfoxLogger.for_extras("RewindableRandomNumberGenerator")
24 |
25 | func _init(p_seed: int):
26 | _rng = RandomNumberGenerator.new()
27 | _rng.set_seed(p_seed)
28 |
29 | ## Returns a pseudo-random float between [code]0.0[/code] and [code]1.0[/code]
30 | ## (inclusive).
31 | func randf() -> float:
32 | _ensure_state()
33 | return _rng.randf()
34 |
35 | ## Returns a pseudo-random float between [code]from[/code] and [code]to[/code]
36 | ## (inclusive).
37 | func randf_range(from: float, to: float) -> float:
38 | _ensure_state()
39 | return _rng.randf_range(from, to)
40 |
41 | ## Returns a normally-distributed, pseudo-random floating-point number from the
42 | ## specified [code]mean[/code] and a standard [code]deviation[/code]. This is
43 | ## also known as a Gaussian distribution.
44 | func randfn(mean: float = 0.0, deviation: float = 1.0) -> float:
45 | _ensure_state()
46 | return _rng.randfn(mean, deviation)
47 |
48 | ## Returns a pseudo-random 32-bit unsigned integer between [code]0[/code] and
49 | ## [code]4294967295[/code] (inclusive).
50 | func randi() -> int:
51 | _ensure_state()
52 | return _rng.randi()
53 |
54 | ## Returns a pseudo-random 32-bit unsigned integer between [code]from[/code] and
55 | ## [code]to[/code] (inclusive).
56 | func randi_range(from: int, to: int) -> int:
57 | _ensure_state()
58 | return _rng.randi_range(from, to)
59 |
60 | func _ensure_state() -> void:
61 | if NetworkTime.tick == _last_reset_tick and NetworkRollback.tick == _last_reset_rollback_tick:
62 | # State already has been set
63 | return
64 |
65 | if NetworkRollback.is_rollback():
66 | _rng.state = hash([_rng.seed, NetworkRollback.tick])
67 | else:
68 | _rng.state = hash([_rng.seed, NetworkTime.tick])
69 |
70 | _last_reset_rollback_tick = NetworkRollback.tick
71 | _last_reset_tick = NetworkTime.tick
72 |
--------------------------------------------------------------------------------
/addons/netfox/properties/property-snapshot.gd:
--------------------------------------------------------------------------------
1 | extends RefCounted
2 | class_name _PropertySnapshot
3 |
4 | # Maps property paths to their values
5 | # Dictionary[String, Variant]
6 | var _snapshot: Dictionary = {}
7 |
8 | static var _logger := _NetfoxLogger.for_netfox("PropertySnapshot")
9 |
10 | func as_dictionary() -> Dictionary:
11 | return _snapshot.duplicate()
12 |
13 | static func from_dictionary(data: Dictionary) -> _PropertySnapshot:
14 | return _PropertySnapshot.new(data)
15 |
16 | func set_value(property_path: String, data: Variant) -> void:
17 | _snapshot[property_path] = data
18 |
19 | func get_value(property_path: String) -> Variant:
20 | return _snapshot.get(property_path)
21 |
22 | func properties() -> Array:
23 | return _snapshot.keys()
24 |
25 | func has(property_path: String) -> bool:
26 | return _snapshot.has(property_path)
27 |
28 | func size() -> int:
29 | return _snapshot.size()
30 |
31 | func equals(other: _PropertySnapshot):
32 | return _snapshot == other._snapshot
33 |
34 | func is_empty() -> bool:
35 | return _snapshot.is_empty()
36 |
37 | func apply(cache: PropertyCache) -> void:
38 | for property_path in _snapshot:
39 | var property_entry := cache.get_entry(property_path)
40 | var value = _snapshot[property_path]
41 | property_entry.set_value(value)
42 |
43 | func merge(data: _PropertySnapshot) -> _PropertySnapshot:
44 | var result := _snapshot.duplicate()
45 | for key in data.as_dictionary():
46 | result[key] = data._snapshot[key]
47 |
48 | return _PropertySnapshot.from_dictionary(result)
49 |
50 | func make_patch(data: _PropertySnapshot) -> _PropertySnapshot:
51 | var result := {}
52 |
53 | for property_path in data.properties():
54 | var old_property = get_value(property_path)
55 | var new_property = data.get_value(property_path)
56 |
57 | if old_property != new_property:
58 | result[property_path] = new_property
59 |
60 | return _PropertySnapshot.from_dictionary(result)
61 |
62 | func sanitize(sender: int, property_cache: PropertyCache) -> void:
63 | var sanitized := {}
64 |
65 | for property in _snapshot.keys():
66 | var property_entry := property_cache.get_entry(property)
67 | var authority := property_entry.node.get_multiplayer_authority()
68 |
69 | if authority == sender:
70 | sanitized[property] = _snapshot[property]
71 | else:
72 | _logger.warning(
73 | "Received data for property %s, owned by %s, from sender %s",
74 | [ property, authority, sender ]
75 | )
76 |
77 | _snapshot = sanitized
78 |
79 | static func extract(properties: Array[PropertyEntry]) -> _PropertySnapshot:
80 | var result = {}
81 | for property in properties:
82 | result[property.to_string()] = property.get_value()
83 | return _PropertySnapshot.from_dictionary(result)
84 |
85 | func _init(p_snapshot: Dictionary = {}) -> void:
86 | _snapshot = p_snapshot
87 |
--------------------------------------------------------------------------------
/addons/netfox.extras/physics/physics-driver-toggles.gd:
--------------------------------------------------------------------------------
1 | extends Object
2 |
3 | class PhysicsDriverToggle:
4 | const INACTIVE_SUFFIX := ".off"
5 |
6 | func get_name() -> String:
7 | return "???"
8 |
9 | func get_files() -> Array[String]:
10 | return []
11 |
12 | func get_error_messages() -> Array[String]:
13 | return []
14 |
15 | func is_enabled() -> bool:
16 | return get_files().any(func(it): return FileAccess.file_exists(it))
17 |
18 | func toggle() -> Array[String]:
19 | var errors := get_error_messages()
20 | if not errors.is_empty():
21 | return errors
22 |
23 | var enable := not is_enabled()
24 |
25 | var uid_files := get_files().map(func(it): return it + ".uid")
26 | var renames = (get_files() + uid_files).map(func(it):
27 | if enable: return [it + INACTIVE_SUFFIX, it]
28 | else: return [it, it + INACTIVE_SUFFIX]
29 | )
30 |
31 | for rename in renames:
32 | var result := DirAccess.rename_absolute(rename[0], rename[1])
33 | if result != OK:
34 | errors.append(
35 | "Failed rename \"%s\" -> \"%s\"; reason: %s" %
36 | [rename[0], rename[1], error_string(result)]
37 | )
38 | return errors
39 |
40 | class Rapier2DPhysicsDriverToggle extends PhysicsDriverToggle:
41 | func get_name() -> String:
42 | return "Rapier2D"
43 |
44 | func get_files() -> Array[String]:
45 | return [
46 | "res://addons/netfox.extras/physics/rapier_driver_2d.gd",
47 | ]
48 |
49 | func get_error_messages() -> Array[String]:
50 | if not ClassDB.class_exists("RapierPhysicsServer2D"):
51 | return ["Rapier physics is not available! Is the extension installed?"]
52 | return []
53 |
54 | class Rapier3DPhysicsDriverToggle extends PhysicsDriverToggle:
55 | func get_name() -> String:
56 | return "Rapier3D"
57 |
58 | func get_files() -> Array[String]:
59 | return [
60 | "res://addons/netfox.extras/physics/rapier_driver_3d.gd",
61 | ]
62 |
63 | func get_error_messages() -> Array[String]:
64 | if not ClassDB.class_exists("RapierPhysicsServer3D"):
65 | return ["Rapier physics is not available! Is the extension installed?"]
66 | return []
67 |
68 | class GodotPhysicsDriverToggle extends PhysicsDriverToggle:
69 | func get_name() -> String:
70 | return "Godot"
71 |
72 | func get_files() -> Array[String]:
73 | return [
74 | "res://addons/netfox.extras/physics/godot_driver_3d.gd",
75 | "res://addons/netfox.extras/physics/godot_driver_2d.gd"
76 | ]
77 |
78 | func get_error_messages() -> Array[String]:
79 | if not PhysicsServer3D.has_method("space_step") or not PhysicsServer2D.has_method("space_step"):
80 | return ["Physics stepping is not available! Is this the right Godot build?"]
81 | return []
82 |
83 | static func all() -> Array[PhysicsDriverToggle]:
84 | return [
85 | Rapier2DPhysicsDriverToggle.new(),
86 | Rapier3DPhysicsDriverToggle.new(),
87 | GodotPhysicsDriverToggle.new()
88 | ]
89 |
--------------------------------------------------------------------------------
/addons/netfox.extras/physics/physics_driver.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 |
3 | class_name PhysicsDriver
4 |
5 | # Physics driver based on netfox ticks
6 | # Step physics in time with netfox and participates in rollback
7 |
8 | var physics_space: RID
9 | var snapshots: Dictionary = {}
10 |
11 | # Number of physics steps to take per network tick
12 | @export var physics_factor: int = 2
13 | # Snapshot and Rollback entire physics space.
14 | @export var rollback_physics_space: bool = true
15 |
16 | func _enter_tree():
17 | #regular ticks
18 | NetworkTime.before_tick.connect(before_tick)
19 | NetworkTime.after_tick_loop.connect(after_tick_loop)
20 |
21 | #rollback ticks
22 | if rollback_physics_space:
23 | NetworkRollback.on_prepare_tick.connect(on_prepare_tick)
24 | NetworkRollback.on_process_tick.connect(on_process_tick)
25 |
26 | func _exit_tree():
27 | NetworkTime.before_tick.disconnect(before_tick)
28 | NetworkTime.after_tick_loop.disconnect(after_tick_loop)
29 |
30 | #rollback ticks
31 | if NetworkRollback.on_prepare_tick.is_connected(on_prepare_tick):
32 | NetworkRollback.on_prepare_tick.disconnect(on_prepare_tick)
33 | NetworkRollback.on_process_tick.disconnect(on_process_tick)
34 |
35 | func _ready() -> void:
36 | _init_physics_space()
37 |
38 | # Emitted before a tick is run.
39 | func before_tick(_delta: float, tick: int) -> void:
40 | _snapshot_space(tick)
41 | step_physics(_delta)
42 |
43 | func on_prepare_tick(tick: int) -> void:
44 | if NetworkRollback._rollback_from == tick:
45 | # First tick of rollback loop, rewind
46 | _rollback_space(tick)
47 | else:
48 | # Subsequent ticks are re-writing history.
49 | _snapshot_space(tick)
50 |
51 | func on_process_tick(_tick: int) -> void:
52 | step_physics(NetworkTime.ticktime)
53 |
54 | func after_tick_loop() -> void:
55 | # Remove old snapshots
56 | for i in snapshots.keys():
57 | if i < NetworkRollback.history_start:
58 | snapshots.erase(i)
59 |
60 | func step_physics(_delta: float) -> void:
61 | # Break up physics into smaller steps if needed
62 | var frac_delta = _delta / physics_factor
63 | var rollback_participants = get_tree().get_nodes_in_group("network_rigid_body")
64 | for i in range(physics_factor):
65 | for net_rigid_body in rollback_participants:
66 | net_rigid_body._physics_rollback_tick(frac_delta, NetworkTime.tick)
67 |
68 | _physics_step(frac_delta)
69 |
70 | ## Override this method to initialize the physics space.
71 | func _init_physics_space() -> void:
72 | pass
73 |
74 | ## Override this method to take one step in the physics space.
75 | ## [br][br]
76 | ## It should also flush and update all Godot nodes
77 | func _physics_step(_delta) -> void:
78 | pass
79 |
80 | ## Override this method to record the current state of the physics space.
81 | func _snapshot_space(_tick: int) -> void:
82 | pass
83 |
84 | ## Override this method to restore the physics space to a previous state.
85 | func _rollback_space(_tick) -> void:
86 | pass
87 |
--------------------------------------------------------------------------------
/addons/netfox.extras/physics/godot_driver_3d.gd:
--------------------------------------------------------------------------------
1 | extends PhysicsDriver
2 | class_name PhysicsDriver3D
3 |
4 | # Physics driver based on netfox ticks
5 | # Requires a custom build of Godot with https://github.com/godotengine/godot/pull/76462
6 |
7 | # Maps ticks ( int ) to global snapshots ( Dictionary )
8 | var scene_collision_objects: Array = []
9 |
10 | func _init_physics_space() -> void:
11 | physics_space = get_viewport().world_3d.space
12 | PhysicsServer3D.space_set_active(physics_space, false)
13 |
14 | get_tree().node_added.connect(node_added)
15 | get_tree().node_removed.connect(node_removed)
16 | scan_tree()
17 |
18 | func _physics_step(delta) -> void:
19 | PhysicsServer3D.space_flush_queries(physics_space)
20 | PhysicsServer3D.space_step(physics_space, delta)
21 |
22 | func _snapshot_space(tick: int) -> void:
23 | # Maps RIDs to physics state ( Array )
24 | var rid_states := {}
25 | for element in scene_collision_objects:
26 | var rid = element.get_rid()
27 | rid_states[rid] = get_body_states(rid)
28 |
29 | snapshots[tick] = rid_states
30 |
31 | func _rollback_space(tick) -> void:
32 | if snapshots.has(tick):
33 | var rid_states = snapshots[tick]
34 | for rid in rid_states.keys():
35 | set_body_states(rid, rid_states[rid])
36 |
37 | for body in scene_collision_objects:
38 | if body is CharacterBody3D or body is AnimatableBody3D:
39 | body.force_update_transform()
40 |
41 |
42 | func get_body_states(rid: RID) -> Array:
43 | var body_state: Array = [Vector3.ZERO, Quaternion.IDENTITY, Vector3.ZERO, Vector3.ZERO]
44 | body_state[0] = PhysicsServer3D.body_get_state(rid, PhysicsServer3D.BODY_STATE_TRANSFORM)
45 | body_state[1] = PhysicsServer3D.body_get_state(rid, PhysicsServer3D.BODY_STATE_LINEAR_VELOCITY)
46 | body_state[2] = PhysicsServer3D.body_get_state(rid, PhysicsServer3D.BODY_STATE_ANGULAR_VELOCITY)
47 | body_state[3] = PhysicsServer3D.body_get_state(rid, PhysicsServer3D.BODY_STATE_SLEEPING)
48 | return body_state
49 |
50 | func set_body_states(rid: RID, body_state: Array) -> void:
51 | PhysicsServer3D.body_set_state(rid, PhysicsServer3D.BODY_STATE_TRANSFORM, body_state[0])
52 | PhysicsServer3D.body_set_state(rid, PhysicsServer3D.BODY_STATE_LINEAR_VELOCITY, body_state[1])
53 | PhysicsServer3D.body_set_state(rid, PhysicsServer3D.BODY_STATE_ANGULAR_VELOCITY, body_state[2])
54 | PhysicsServer3D.body_set_state(rid, PhysicsServer3D.BODY_STATE_SLEEPING, body_state[3])
55 |
56 | func scan_tree():
57 | scene_collision_objects.clear()
58 | scene_collision_objects = get_all_children(get_node('/root'))
59 |
60 | func get_all_children(in_node: Node) -> Array:
61 | var nodes = []
62 | nodes = in_node.find_children("*", "PhysicsBody3D", true, false)
63 | return nodes
64 |
65 | func node_added(node: Node) -> void:
66 | if node is PhysicsBody3D:
67 | scene_collision_objects.append(node)
68 |
69 | func node_removed(node: Node) -> void:
70 | if node is PhysicsBody3D:
71 | scene_collision_objects.erase(node)
72 |
--------------------------------------------------------------------------------
/scenes/confetti.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=14 format=3 uid="uid://ds50rqn0gl2jl"]
2 |
3 | [ext_resource type="Script" uid="uid://bxr4tbk4dq8v0" path="res://scripts/confetti.gd" id="1_xjk0n"]
4 |
5 | [sub_resource type="Curve" id="Curve_du648"]
6 | _data = [Vector2(0.0107817, 1), 0.0, 0.0, 0, 0, Vector2(0.762803, 0.801996), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0]
7 | point_count = 3
8 |
9 | [sub_resource type="CurveTexture" id="CurveTexture_xjk0n"]
10 | curve = SubResource("Curve_du648")
11 |
12 | [sub_resource type="Gradient" id="Gradient_xejpt"]
13 | colors = PackedColorArray(0.779291, 0.779291, 0.779291, 0.329412, 1, 1, 1, 1)
14 |
15 | [sub_resource type="GradientTexture1D" id="GradientTexture1D_2hbhx"]
16 | gradient = SubResource("Gradient_xejpt")
17 |
18 | [sub_resource type="Gradient" id="Gradient_e8opl"]
19 | offsets = PackedFloat32Array(0, 0.501449, 1)
20 | colors = PackedColorArray(1, 0, 0, 1, 0.38556, 0.57763, 1, 1, 1, 1, 0, 1)
21 |
22 | [sub_resource type="GradientTexture1D" id="GradientTexture1D_v30ou"]
23 | gradient = SubResource("Gradient_e8opl")
24 |
25 | [sub_resource type="Curve" id="Curve_k3xkt"]
26 | _data = [Vector2(0, 0.624475), 0.0, 0.0, 0, 0, Vector2(0.490566, 1), 0.0, 0.0, 0, 0, Vector2(1, 0.63813), 0.0, 0.0, 0, 0]
27 | point_count = 3
28 |
29 | [sub_resource type="CurveTexture" id="CurveTexture_lnt6a"]
30 | curve = SubResource("Curve_k3xkt")
31 |
32 | [sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_du648"]
33 | particle_flag_align_y = true
34 | angle_min = -360.0
35 | angle_max = 360.0
36 | direction = Vector3(0, 0, 0)
37 | spread = 180.0
38 | initial_velocity_min = 5.0
39 | initial_velocity_max = 20.0
40 | linear_accel_min = 1.2
41 | linear_accel_max = 14.94
42 | color_ramp = SubResource("GradientTexture1D_v30ou")
43 | color_initial_ramp = SubResource("GradientTexture1D_2hbhx")
44 | alpha_curve = SubResource("CurveTexture_xjk0n")
45 | emission_curve = SubResource("CurveTexture_lnt6a")
46 | hue_variation_min = -0.12
47 | hue_variation_max = 0.14
48 |
49 | [sub_resource type="Curve" id="Curve_xjk0n"]
50 | _data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0]
51 | point_count = 2
52 |
53 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_xjk0n"]
54 | vertex_color_use_as_albedo = true
55 | emission = Color(1, 1, 1, 1)
56 | emission_energy_multiplier = 4.15
57 | emission_operator = 1
58 | billboard_keep_scale = true
59 |
60 | [sub_resource type="RibbonTrailMesh" id="RibbonTrailMesh_xejpt"]
61 | material = SubResource("StandardMaterial3D_xjk0n")
62 | size = 0.1
63 | sections = 6
64 | curve = SubResource("Curve_xjk0n")
65 |
66 | [node name="Confetti" type="GPUParticles3D"]
67 | emitting = false
68 | amount = 300
69 | lifetime = 3.0
70 | one_shot = true
71 | explosiveness = 1.0
72 | process_material = SubResource("ParticleProcessMaterial_du648")
73 | draw_pass_1 = SubResource("RibbonTrailMesh_xejpt")
74 | script = ExtResource("1_xjk0n")
75 |
76 | [connection signal="finished" from="." to="." method="_on_finished"]
77 |
--------------------------------------------------------------------------------
/addons/netfox/icons/rewindable-action.svg:
--------------------------------------------------------------------------------
1 |
2 |
90 |
--------------------------------------------------------------------------------
/addons/netfox.extras/physics/godot_driver_2d.gd:
--------------------------------------------------------------------------------
1 | extends PhysicsDriver
2 |
3 | class_name PhysicsDriver2D
4 |
5 | # Physics driver based on netfox ticks
6 | # Requires a custom build of Godot with https://github.com/godotengine/godot/pull/76462
7 |
8 | var scene_collision_objects: Array = []
9 | var collision_objects_snapshots: Dictionary[int, Dictionary] = {}
10 |
11 | func _init_physics_space() -> void:
12 | physics_space = get_viewport().world_2d.space
13 | PhysicsServer2D.space_set_active(physics_space, false)
14 |
15 | get_tree().node_added.connect(node_added)
16 | get_tree().node_removed.connect(node_removed)
17 | scan_tree()
18 |
19 | func _physics_step(delta) -> void:
20 | PhysicsServer2D.space_flush_queries(physics_space)
21 | PhysicsServer2D.space_step(physics_space, delta)
22 |
23 | func _snapshot_space(tick: int) -> void:
24 | var rid_states: Dictionary[RID, Array] = {}
25 | for element in scene_collision_objects:
26 | if element is CharacterBody2D:
27 | element.force_update_transform() # force colliders to update
28 |
29 | var rid = element.get_rid()
30 | rid_states[rid] = get_body_states(rid)
31 | snapshots[tick] = rid_states
32 |
33 | func _rollback_space(tick) -> void:
34 | if snapshots.has(tick):
35 | var rid_states = snapshots[tick]
36 | for rid in rid_states.keys():
37 | set_body_states(rid, rid_states[rid])
38 |
39 | for body in scene_collision_objects:
40 | if body is CharacterBody2D or body is AnimatableBody2D:
41 | body.force_update_transform() # force colliders to update
42 |
43 | func get_body_states(rid: RID) -> Array:
44 | var body_state: Array = [Vector3.ZERO, Quaternion.IDENTITY, Vector3.ZERO, Vector3.ZERO]
45 | body_state[0] = PhysicsServer2D.body_get_state(rid, PhysicsServer2D.BODY_STATE_TRANSFORM)
46 | body_state[1] = PhysicsServer2D.body_get_state(rid, PhysicsServer2D.BODY_STATE_LINEAR_VELOCITY)
47 | body_state[2] = PhysicsServer2D.body_get_state(rid, PhysicsServer2D.BODY_STATE_ANGULAR_VELOCITY)
48 | body_state[3] = PhysicsServer2D.body_get_state(rid, PhysicsServer2D.BODY_STATE_SLEEPING)
49 | return body_state
50 |
51 | func set_body_states(rid: RID, body_state: Array) -> void:
52 | PhysicsServer2D.body_set_state(rid, PhysicsServer2D.BODY_STATE_TRANSFORM, body_state[0])
53 | PhysicsServer2D.body_set_state(rid, PhysicsServer2D.BODY_STATE_LINEAR_VELOCITY, body_state[1])
54 | PhysicsServer2D.body_set_state(rid, PhysicsServer2D.BODY_STATE_ANGULAR_VELOCITY, body_state[2])
55 | PhysicsServer2D.body_set_state(rid, PhysicsServer2D.BODY_STATE_SLEEPING, body_state[3])
56 |
57 | func scan_tree():
58 | scene_collision_objects.clear()
59 | scene_collision_objects = get_all_children(get_node('/root'))
60 |
61 | func get_all_children(in_node: Node) -> Array:
62 | var nodes = []
63 | nodes = in_node.find_children("*", "PhysicsBody2D", true, false)
64 | return nodes
65 |
66 | func node_added(node: Node) -> void:
67 | if node is PhysicsBody2D:
68 | scene_collision_objects.append(node)
69 |
70 | func node_removed(node: Node) -> void:
71 | if node is PhysicsBody2D:
72 | scene_collision_objects.erase(node)
73 |
--------------------------------------------------------------------------------
/addons/netfox.extras/icons/rewindable-state-machine.svg:
--------------------------------------------------------------------------------
1 |
2 |
90 |
--------------------------------------------------------------------------------
/addons/netfox/time/network-tickrate-handshake.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 | class_name NetworkTickrateHandshake
3 |
4 | ## Internal class to manage the tickrate handshake.
5 | ##
6 | ## Whenever a new peer joins, they exchange their configured tickrate with the
7 | ## host. If the tickrate mismatches, a warning is emitted by default, as this is
8 | ## assumed to be a developer mistake.
9 | ## [br][br]
10 | ## However, if this is expected, different actions can be configured.
11 |
12 | ## Emit a warning on tickrate mismatch
13 | const WARN := 0
14 |
15 | ## Disconnect peer on tickrate mismatch[br]
16 | ## This is enforced by the host.
17 | const DISCONNECT := 1
18 |
19 | ## Adjust tickrate to the host's on mismatch
20 | const ADJUST := 2
21 |
22 | ## Emit [signal on_tickrate_mismatch] on mismatch[br]
23 | ## This is emitted on both host and client.
24 | const SIGNAL := 3
25 |
26 | ## Configures what happens on a tickrate mismatch.[br]
27 | ## Defaults to [constant WARN], based on project settings.
28 | var mismatch_action: int = ProjectSettings.get_setting(&"netfox/time/tickrate_mismatch_action", WARN)
29 |
30 | static var _logger := _NetfoxLogger.for_netfox("NetworkTickrateHandshake")
31 |
32 | ## Emitted when a tickrate mismatch is encountered, and [member mismatch_action] is set to
33 | ## [constant SIGNAL].
34 | signal on_tickrate_mismatch(peer: int, tickrate: int)
35 |
36 | ## Run the tickrate handshake.
37 | ## [br][br]
38 | ## This will connect to signals, so that every new peer receives tickrate info
39 | ## from the host.
40 | ## [br][br]
41 | ## Called by [_NetworkTime], no need to call manually.
42 | func run() -> void:
43 | if multiplayer.is_server():
44 | # Broadcast tickrate
45 | _submit_tickrate.rpc(NetworkTime.tickrate)
46 |
47 | # Submit tickrate to anyone joining
48 | multiplayer.peer_connected.connect(_handle_new_peer)
49 | else:
50 | # Submit tickrate to host
51 | _submit_tickrate.rpc_id(1, NetworkTime.tickrate)
52 |
53 | ## Stop the tickrate handshake.
54 | ## [br][br]
55 | ## Called by [_NetworkTime], no need to call manually.
56 | func stop() -> void:
57 | if multiplayer.is_server():
58 | multiplayer.peer_connected.disconnect(_handle_new_peer)
59 |
60 | func _ready() -> void:
61 | name = "NetworkTickrateHandshake"
62 |
63 | func _handle_new_peer(peer: int) -> void:
64 | if multiplayer.is_server():
65 | _submit_tickrate.rpc_id(peer, NetworkTime.tickrate)
66 |
67 | func _handle_tickrate_mismatch(peer: int, tickrate: int) -> void:
68 | match mismatch_action:
69 | WARN:
70 | _logger.warning(
71 | "Local tickrate %dtps differs from tickrate of peer #%d at %dtps! " +
72 | "Make sure that tickrates are correctly configured in the Project settings! " +
73 | "See netfox/Time/Tickrate.", [
74 | NetworkTime.tickrate, peer, tickrate
75 | ])
76 | DISCONNECT:
77 | if multiplayer.is_server():
78 | _logger.warning("Peer #%d's tickrate of %dtps differs from expected %dtps! Disconnecting.", [
79 | peer, tickrate, NetworkTime.tickrate
80 | ])
81 | multiplayer.multiplayer_peer.disconnect_peer(peer)
82 | ADJUST:
83 | if not multiplayer.is_server():
84 | _logger.info("Local tickrate %dtps differs from tickrate of host at %dtps! Adjusting.", [
85 | NetworkTime.tickrate, tickrate
86 | ])
87 | # TODO: Make tickrate mutable at user's digression
88 | ProjectSettings.set_setting(&"netfox/time/tickrate", tickrate)
89 | SIGNAL:
90 | on_tickrate_mismatch.emit(peer, tickrate)
91 |
92 | @rpc("any_peer", "reliable", "call_remote")
93 | func _submit_tickrate(tickrate: int) -> void:
94 | var sender := multiplayer.get_remote_sender_id()
95 | _logger.debug("Received tickrate %d from peer %d", [tickrate, sender])
96 |
97 | if tickrate != NetworkTime.tickrate:
98 | _handle_tickrate_mismatch(sender, tickrate)
99 |
--------------------------------------------------------------------------------
/addons/netfox/interpolators.gd:
--------------------------------------------------------------------------------
1 | extends Object
2 | class_name Interpolators
3 |
4 | class Interpolator:
5 | var is_applicable: Callable
6 | var apply: Callable
7 |
8 | static func make(is_applicable: Callable, apply: Callable) -> Interpolator:
9 | var result := Interpolator.new()
10 | result.is_applicable = is_applicable
11 | result.apply = apply
12 | return result
13 |
14 | static var DEFAULT_INTERPOLATOR := Interpolator.make(
15 | func (v): return true,
16 | func (a, b, f): return a if f < 0.5 else b
17 | )
18 |
19 | static var interpolators: Array[Interpolator]
20 | static var default_apply: Callable = func(a, b, f): a if f < 0.5 else b
21 |
22 | ## Register an interpolator.
23 | ##
24 | ## New interpolators are pushed to the front of the list, making them have
25 | ## precedence over existing ones. This can be useful in case you want to override
26 | ## the built-in interpolators.
27 | static func register(is_applicable: Callable, apply: Callable) -> void:
28 | interpolators.push_front(Interpolator.make(is_applicable, apply))
29 |
30 | ## Find the appropriate interpolator for the given value.
31 | ##
32 | ## If none was found, the default interpolator is returned.
33 | static func find_for(value) -> Callable:
34 | for interpolator in interpolators:
35 | if interpolator.is_applicable.call(value):
36 | return interpolator.apply
37 |
38 | return DEFAULT_INTERPOLATOR.apply
39 |
40 | ## Interpolate between two values.
41 | ##
42 | ## Note, that it is usually faster to just cache the Callable returned by find_for
43 | ## and call that, instead of calling interpolate repeatedly. The latter will have
44 | ## to lookup the appropriate interpolator on every call.
45 | static func interpolate(a, b, f: float):
46 | return find_for(a).call(a, b, f)
47 |
48 | static func _static_init() -> void:
49 | # Register built-in interpolators
50 | # Float
51 | register(
52 | func(a): return a is float,
53 | func(a: float, b: float, f: float): return lerpf(a, b, f)
54 | )
55 |
56 | # Int
57 | register(
58 | func(a): return a is int,
59 | func(a: int, b: int, f: float): return int(lerpf(a, b, f))
60 | )
61 |
62 | # Vector
63 | register(
64 | func(a): return a is Vector2,
65 | func(a: Vector2, b: Vector2, f: float): return a.lerp(b, f)
66 | )
67 | register(
68 | func(a): return a is Vector3,
69 | func(a: Vector3, b: Vector3, f: float): return a.lerp(b, f)
70 | )
71 | register(
72 | func(a): return a is Vector4,
73 | func(a: Vector4, b: Vector4, f: float): return a.lerp(b, f)
74 | )
75 |
76 | register(
77 | func(a): return a is Vector2i,
78 | func(a: Vector2i, b: Vector2i, f: float): return Vector2i(Vector2(a).lerp(b, f))
79 | )
80 | register(
81 | func(a): return a is Vector3i,
82 | func(a: Vector3i, b: Vector3i, f: float): return Vector3i(Vector3(a).lerp(b, f))
83 | )
84 | register(
85 | func(a): return a is Vector4i,
86 | func(a: Vector4i, b: Vector4i, f: float): return Vector4i(Vector4(a).lerp(b, f))
87 | )
88 |
89 | # Transform
90 | register(
91 | func(a): return a is Transform2D,
92 | func(a: Transform2D, b: Transform2D, f: float): return a.interpolate_with(b, f)
93 | )
94 | register(
95 | func(a): return a is Transform3D,
96 | func(a: Transform3D, b: Transform3D, f: float): return a.interpolate_with(b, f)
97 | )
98 |
99 | # Quaternion
100 | register(
101 | func(a): return a is Quaternion,
102 | func(a: Quaternion, b: Quaternion, f: float): return a.slerp(b, f)
103 | )
104 |
105 | # Basis
106 | register(
107 | func(a): return a is Basis,
108 | func(a: Basis, b: Basis, f: float): return a.slerp(b, f)
109 | )
110 |
111 | # Color
112 | register(
113 | func(a): return a is Color,
114 | func(a: Color, b: Color, f: float): return a.lerp(b, f)
115 | )
116 |
--------------------------------------------------------------------------------
/project.godot:
--------------------------------------------------------------------------------
1 | ; Engine configuration file.
2 | ; It's best edited using the editor UI and not directly,
3 | ; since the parameters that go here are not all obvious.
4 | ;
5 | ; Format:
6 | ; [section] ; section goes between []
7 | ; param=value ; assign values to parameters
8 |
9 | config_version=5
10 |
11 | [application]
12 |
13 | config/name="Physics Test"
14 | run/main_scene="res://scenes/main.tscn"
15 | config/features=PackedStringArray("4.5", "Forward Plus")
16 | config/icon="res://icon.svg"
17 |
18 | [autoload]
19 |
20 | WindowTiler="*res://addons/netfox.extras/window-tiler.gd"
21 | Noray="*res://addons/netfox.noray/noray.gd"
22 | PacketHandshake="*res://addons/netfox.noray/packet-handshake.gd"
23 | NetworkTime="*res://addons/netfox/network-time.gd"
24 | NetworkTimeSynchronizer="*res://addons/netfox/network-time-synchronizer.gd"
25 | NetworkRollback="*res://addons/netfox/rollback/network-rollback.gd"
26 | NetworkEvents="*res://addons/netfox/network-events.gd"
27 | NetworkPerformance="*res://addons/netfox/network-performance.gd"
28 | NetworkSimulator="*res://addons/netfox.extras/network-simulator.gd"
29 |
30 | [editor_plugins]
31 |
32 | enabled=PackedStringArray("res://addons/netfox.extras/plugin.cfg", "res://addons/netfox.internals/plugin.cfg", "res://addons/netfox/plugin.cfg")
33 |
34 | [global_group]
35 |
36 | players=""
37 |
38 | [input]
39 |
40 | up={
41 | "deadzone": 0.5,
42 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
43 | ]
44 | }
45 | down={
46 | "deadzone": 0.5,
47 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
48 | ]
49 | }
50 | left={
51 | "deadzone": 0.5,
52 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
53 | ]
54 | }
55 | right={
56 | "deadzone": 0.5,
57 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
58 | ]
59 | }
60 | quit={
61 | "deadzone": 0.5,
62 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194305,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
63 | ]
64 | }
65 | jump={
66 | "deadzone": 0.2,
67 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null)
68 | ]
69 | }
70 |
71 | [layer_names]
72 |
73 | 3d_physics/layer_1="floor"
74 | 3d_physics/layer_2="player"
75 | 3d_physics/layer_3="ball"
76 | 3d_physics/layer_4="goal"
77 |
78 | [netfox]
79 |
80 | extras/auto_tile_windows=true
81 | extras/borderless=true
82 | extras/screen=1
83 |
84 | [physics]
85 |
86 | common/max_physics_steps_per_frame=64
87 | 3d/physics_engine="Jolt Physics"
88 |
--------------------------------------------------------------------------------
/addons/netfox/encoder/redundant-history-encoder.gd:
--------------------------------------------------------------------------------
1 | extends RefCounted
2 | class_name _RedundantHistoryEncoder
3 |
4 | var redundancy: int = 4:
5 | get = get_redundancy,
6 | set = set_redundancy
7 |
8 | var _history: _PropertyHistoryBuffer
9 | var _properties: Array[PropertyEntry]
10 | var _property_cache: PropertyCache
11 |
12 | var _version := 0
13 | var _has_received := false
14 |
15 | var _logger := _NetfoxLogger.for_netfox("RedundantHistoryEncoder")
16 |
17 | func get_redundancy() -> int:
18 | return redundancy
19 |
20 | func set_redundancy(p_redundancy: int):
21 | if p_redundancy <= 0:
22 | _logger.warning(
23 | "Attempting to set redundancy to %d, which would send no data!", [p_redundancy]
24 | )
25 | return
26 |
27 | redundancy = p_redundancy
28 |
29 | func set_properties(properties: Array[PropertyEntry]) -> void:
30 | if _properties != properties:
31 | _version = (_version + 1) % 256
32 | _properties = properties.duplicate()
33 |
34 | func encode(tick: int, properties: Array[PropertyEntry]) -> Array:
35 | if _history.is_empty():
36 | return []
37 | var data := []
38 |
39 | for i in range(mini(redundancy, _history.size())):
40 | var offset_tick := tick - i
41 | if offset_tick < _history.get_earliest_tick():
42 | break
43 |
44 | var snapshot := _history.get_snapshot(offset_tick)
45 | for property in properties:
46 | data.append(snapshot.get_value(property.to_string()))
47 |
48 | data.append(_version)
49 | return data
50 |
51 | func decode(data: Array, properties: Array[PropertyEntry]) -> Array[_PropertySnapshot]:
52 | if data.is_empty() or properties.is_empty():
53 | return []
54 |
55 | var packet_version := data.pop_back() as int
56 |
57 | if packet_version != _version:
58 | if not _has_received:
59 | # First packet, assume version is OK
60 | _version = packet_version
61 | else:
62 | # Version mismatch, can't parse
63 | _logger.warning("Version mismatch! own: %d, received: %s", [_version, packet_version])
64 | return []
65 |
66 | var result: Array[_PropertySnapshot] = []
67 | var redundancy := data.size() / properties.size()
68 | result.assign(range(redundancy)
69 | .map(func(__): return _PropertySnapshot.new())
70 | )
71 |
72 | for i in range(data.size()):
73 | var offset_idx := i / properties.size()
74 | var prop_idx := i % properties.size()
75 |
76 | result[offset_idx].set_value(properties[prop_idx].to_string(), data[i])
77 |
78 | _has_received = true
79 |
80 | return result
81 |
82 | # Returns earliest new tick as int, or -1 if no new ticks applied
83 | func apply(tick: int, snapshots: Array[_PropertySnapshot], sender: int = 0) -> int:
84 | var earliest_new_tick = -1
85 |
86 | for i in range(snapshots.size()):
87 | var offset_tick := tick - i
88 | var snapshot := snapshots[i]
89 |
90 | if offset_tick < NetworkRollback.history_start:
91 | # Data too old
92 | _logger.warning(
93 | "Received data for %s, rejecting because older than %s frames",
94 | [offset_tick, NetworkRollback.history_limit]
95 | )
96 | continue
97 |
98 | if sender > 0:
99 | snapshot.sanitize(sender, _property_cache)
100 | if snapshot.is_empty():
101 | # No valid properties ( probably after sanitize )
102 | _logger.warning("Received invalid data from %d for tick %d", [sender, tick])
103 | continue
104 |
105 | var known_snapshot := _history.get_snapshot(offset_tick)
106 | if not known_snapshot.equals(snapshot):
107 | # Received a new snapshot, store and emit signal
108 | _history.set_snapshot(offset_tick, snapshot)
109 | earliest_new_tick = offset_tick
110 |
111 | return earliest_new_tick
112 |
113 |
114 | func _init(p_history: _PropertyHistoryBuffer, p_property_cache: PropertyCache):
115 | _history = p_history
116 | _property_cache = p_property_cache
117 |
--------------------------------------------------------------------------------
/addons/netfox.extras/weapon/network-weapon-hitscan-3d.gd:
--------------------------------------------------------------------------------
1 | extends Node3D
2 | class_name NetworkWeaponHitscan3D
3 |
4 | ## A 3D-specific implementation of a networked hitscan (raycast) weapon.
5 |
6 | ## Maximum distance to cast the ray
7 | @export var max_distance: float = 1000.0
8 |
9 | ## Mask used to detect raycast hits
10 | @export_flags_3d_physics var collision_mask: int = 0xFFFFFFFF
11 |
12 | ## Colliders excluded from raycast hits
13 | @export var exclude: Array[RID] = []
14 |
15 | var _weapon: _NetworkWeaponProxy
16 |
17 | ## Try to fire the weapon and return the projectile.
18 | ## [br][br]
19 | ## Returns true if the weapon was fired.
20 | func fire() -> bool:
21 | if not can_fire():
22 | return false
23 |
24 | _apply_data(_get_data())
25 | _after_fire()
26 | return true
27 |
28 | ## Check whether this weapon can be fired.
29 | func can_fire() -> bool:
30 | return _weapon.can_fire()
31 |
32 | func _init():
33 | _weapon = _NetworkWeaponProxy.new()
34 | add_child(_weapon, true, INTERNAL_MODE_BACK)
35 | _weapon.owner = self
36 |
37 | _weapon.c_can_fire = _can_fire
38 | _weapon.c_can_peer_use = _can_peer_use
39 | _weapon.c_after_fire = _after_fire
40 | _weapon.c_spawn = _spawn
41 | _weapon.c_get_data = _get_data
42 | _weapon.c_apply_data = _apply_data
43 | _weapon.c_is_reconcilable = _is_reconcilable
44 | _weapon.c_reconcile = _reconcile
45 |
46 | ## Override this method with your own can fire logic.
47 | ## [br][br]
48 | ## See [NetworkWeapon].
49 | func _can_fire() -> bool:
50 | return true
51 |
52 | ## Override this method to check if a given peer can use this weapon.
53 | ## [br][br]
54 | ## See [NetworkWeapon].
55 | func _can_peer_use(peer_id: int) -> bool:
56 | return true
57 |
58 | ## Override this method to run any logic needed after successfully firing the
59 | ## weapon.
60 | ## [br][br]
61 | ## See [NetworkWeapon].
62 | func _after_fire():
63 | pass
64 |
65 | func _spawn():
66 | # No projectile is spawned for a hitscan weapon.
67 | pass
68 |
69 | func _get_data() -> Dictionary:
70 | # Collect data needed to synchronize the firing event.
71 | return {
72 | "origin": global_transform.origin,
73 | "direction": -global_transform.basis.z # Assuming forward direction.
74 | }
75 |
76 | func _apply_data(data: Dictionary):
77 | # Reproduces the firing event on all peers.
78 | var origin = data["origin"] as Vector3
79 | var direction = data["direction"] as Vector3
80 |
81 | # Perform the raycast from origin in the given direction.
82 | var space_state = get_world_3d().direct_space_state
83 |
84 | # Create a PhysicsRayQueryParameters3D object.
85 | var ray_params = PhysicsRayQueryParameters3D.new()
86 | ray_params.from = origin
87 | ray_params.to = origin + direction * max_distance
88 |
89 | # Set collision masks or exclude objects:
90 | ray_params.collision_mask = collision_mask
91 | ray_params.exclude = exclude
92 |
93 | var result = space_state.intersect_ray(ray_params)
94 |
95 | if result:
96 | # Handle the hit result, such as spawning hit effects.
97 | _on_hit(result)
98 |
99 | # Play firing effects on all peers.
100 | _on_fire()
101 |
102 | func _is_reconcilable(request_data: Dictionary, local_data: Dictionary) -> bool:
103 | # Always reconcilable
104 | return true
105 |
106 | func _reconcile(local_data: Dictionary, remote_data: Dictionary):
107 | # Nothing to do on reconcile
108 | pass
109 |
110 | ## Override to implement raycast hit logic.
111 | ## [br][br]
112 | ## The parameter is the result of a
113 | ## [method PhysicsDirectSpaceState3D.intersect_ray] call.
114 | func _on_hit(result: Dictionary):
115 | # Implement hit effect logic here.
116 | # var hit_position = result.position
117 | # var hit_normal = result.normal
118 | # var collider = result.collider
119 |
120 | # For example, you might emit a signal or instantiate a hit effect scene:
121 | # emit_signal("hit_detected", hit_position, hit_normal, collider)
122 | pass
123 |
124 | ## Override to implement firing effects, like muzzle flash or sound.
125 | func _on_fire():
126 | # Implement firing effect logic here.
127 | pass
128 |
--------------------------------------------------------------------------------
/addons/netfox/rollback/composite/rollback-history-recorder.gd:
--------------------------------------------------------------------------------
1 | extends RefCounted
2 | class_name _RollbackHistoryRecorder
3 |
4 | # Provided externally by RBS
5 | var _state_history: _PropertyHistoryBuffer
6 | var _input_history: _PropertyHistoryBuffer
7 |
8 | var _state_property_config: _PropertyConfig
9 | var _input_property_config: _PropertyConfig
10 |
11 | var _property_cache: PropertyCache
12 |
13 | var _latest_state_tick: int
14 | var _skipset: _Set
15 |
16 | func configure(
17 | p_state_history: _PropertyHistoryBuffer, p_input_history: _PropertyHistoryBuffer,
18 | p_state_property_config: _PropertyConfig, p_input_property_config: _PropertyConfig,
19 | p_property_cache: PropertyCache,
20 | p_skipset: _Set
21 | ) -> void:
22 | _state_history = p_state_history
23 | _input_history = p_input_history
24 | _state_property_config = p_state_property_config
25 | _input_property_config = p_input_property_config
26 | _property_cache = p_property_cache
27 | _skipset = p_skipset
28 |
29 | func set_latest_state_tick(p_latest_state_tick: int) -> void:
30 | _latest_state_tick = p_latest_state_tick
31 |
32 | func apply_state(tick: int) -> void:
33 | # Apply state for tick
34 | var state = _state_history.get_history(tick)
35 | state.apply(_property_cache)
36 |
37 | func apply_display_state() -> void:
38 | apply_state(NetworkRollback.display_tick)
39 |
40 | func apply_tick(tick: int) -> void:
41 | var state := _state_history.get_history(tick)
42 | var input := _input_history.get_history(tick)
43 |
44 | state.apply(_property_cache)
45 | input.apply(_property_cache)
46 |
47 | func trim_history() -> void:
48 | # Trim history
49 | _state_history.trim()
50 | _input_history.trim()
51 |
52 | func record_input(tick: int) -> void:
53 | # Record input
54 | if not _get_recorded_input_props().is_empty():
55 | var input = _PropertySnapshot.extract(_get_recorded_input_props())
56 | var input_tick: int = tick + NetworkRollback.input_delay
57 | _input_history.set_snapshot(input_tick, input)
58 |
59 | func record_state(tick: int) -> void:
60 | # Record state for specified tick ( current + 1 )
61 | # Check if any of the managed nodes were mutated
62 | var is_mutated := _get_recorded_state_props().any(func(pe):
63 | return NetworkRollback.is_mutated(pe.node, tick - 1))
64 |
65 | var record_state := _PropertySnapshot.extract(_get_state_props_to_record(tick))
66 | if record_state.size():
67 | var merge_state := _state_history.get_history(tick - 1)
68 | _state_history.set_snapshot(tick, merge_state.merge(record_state))
69 |
70 | func _should_record_tick(tick: int) -> bool:
71 | if _get_recorded_state_props().is_empty():
72 | # Don't record tick if there's no props to record
73 | return false
74 |
75 | if _get_recorded_state_props().any(func(pe):
76 | return NetworkRollback.is_mutated(pe.node, tick - 1)):
77 | # If there's any node that was mutated, there's something to record
78 | return true
79 |
80 | # Otherwise, record only if we don't have authoritative state for the tick
81 | return tick > _latest_state_tick
82 |
83 | func _get_state_props_to_record(tick: int) -> Array[PropertyEntry]:
84 | if not _should_record_tick(tick):
85 | return []
86 | if _skipset.is_empty():
87 | return _get_recorded_state_props()
88 |
89 | return _get_recorded_state_props().filter(func(pe): return _should_record_property(pe, tick))
90 |
91 | func _should_record_property(property_entry: PropertyEntry, tick: int) -> bool:
92 | if NetworkRollback.is_mutated(property_entry.node, tick - 1):
93 | return true
94 | if _skipset.has(property_entry.node):
95 | return false
96 | return true
97 |
98 | # =============================================================================
99 | # Shared utils, extract later
100 |
101 | func _get_recorded_state_props() -> Array[PropertyEntry]:
102 | return _state_property_config.get_properties()
103 |
104 | func _get_owned_state_props() -> Array[PropertyEntry]:
105 | return _state_property_config.get_owned_properties()
106 |
107 | func _get_recorded_input_props() -> Array[PropertyEntry]:
108 | return _input_property_config.get_owned_properties()
109 |
110 | func _get_owned_input_props() -> Array[PropertyEntry]:
111 | return _input_property_config.get_owned_properties()
112 |
--------------------------------------------------------------------------------
/scripts/player.gd:
--------------------------------------------------------------------------------
1 | extends CharacterBody3D
2 |
3 | class_name Player
4 |
5 | @export var wheel_base: float = 0.6 # distance between front/rear axles
6 | @export var steering_limit: float = 10.0 # front wheel max turning angle (deg)
7 | @export var engine_power: float = -10.0
8 | @export var braking: float = 12.0
9 | @export var friction: float = -2 # friction coefficient
10 | @export var drag: float = -2 # drag coefficient
11 | @export var max_speed_reverse: float = 4
12 | @export var max_speed_forward: float = 15
13 |
14 | # Drifting
15 | @export var slip_speed = 11.0 # lose traction above this speed
16 | @export var traction_slow: float = 0.8 # traction coefficient when not drifting
17 | @export var traction_fast: float = 0.15 # traction coefficient when drifting
18 |
19 |
20 | # Car state properties
21 | @export var acceleration = Vector3.ZERO # current acceleration
22 | @export var drifting = false
23 | @export var direction = 0.0 # current direction, negative is forward
24 | @export var steer_angle = 0.0 # current wheel angle
25 | @export var driver: String = "player"
26 |
27 | var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
28 |
29 | @onready var inputs: PlayerInput = $Input
30 | @onready var rollback_synchronizer: RollbackSynchronizer = $RollbackSynchronizer
31 | #@onready var physics: RapierDriver = get_node(^"../../RapierDriver")
32 |
33 | func _ready():
34 | print(multiplayer.get_unique_id(), " - created ", name, " ready")
35 |
36 | global_position = Vector3(randi_range(0, 3), 2, randi_range(0, 3))
37 |
38 | if str(name).is_valid_int():
39 | inputs.set_multiplayer_authority(str(name).to_int())
40 | focus_camera_on(self)
41 |
42 | await get_tree().process_frame
43 | rollback_synchronizer.process_settings()
44 | NetworkTime.on_tick.connect(on_tick)
45 |
46 | func focus_camera_on(cam_target):
47 | if inputs.is_multiplayer_authority():
48 | var camera = get_tree().get_root().get_camera_3d()
49 | camera.target = cam_target
50 |
51 | func _process(_delta):
52 | $Label3D.text = str(name, "\nmovement: ", str(inputs.motion), "\ninput owner: ", str(inputs.get_multiplayer_authority()))
53 |
54 | func on_tick(delta, _tick):
55 | execute_movement(delta, _tick)
56 |
57 | func _rollback_tick(delta, _tick, _is_fresh):
58 | execute_movement(delta, _tick)
59 |
60 | func execute_movement(delta, _tick):
61 | #update the physics server on where we are this tick.
62 | velocity.y -= gravity * delta
63 | process_input()
64 |
65 | apply_friction(delta)
66 | calculate_steering(delta)
67 |
68 | velocity += acceleration * delta
69 |
70 | move_and_slide()
71 |
72 | func process_input():
73 | var turn = inputs.motion.x
74 | steer_angle = turn * deg_to_rad(steering_limit)
75 | acceleration = Vector3.ZERO
76 | if inputs.motion.y > 0:
77 | acceleration = - transform.basis.z * engine_power
78 | if inputs.motion.y < 0:
79 | acceleration = - transform.basis.z * braking
80 |
81 | func apply_friction(delta):
82 | if velocity.length() < 0.2 and acceleration.length() == 0:
83 | velocity.x = 0
84 | velocity.z = 0
85 | var friction_force = velocity * friction * delta
86 | var drag_force = velocity * drag * delta
87 | acceleration += drag_force + friction_force
88 |
89 |
90 | func calculate_steering(delta):
91 | var rear_wheel = transform.origin + transform.basis.z * wheel_base / 2.0
92 | var front_wheel = transform.origin - transform.basis.z * wheel_base / 2.0
93 | rear_wheel += velocity * delta
94 | front_wheel += velocity.rotated(transform.basis.y, steer_angle) * delta
95 | var new_heading = rear_wheel.direction_to(front_wheel)
96 |
97 | #Drifting
98 | # traction
99 | if not drifting and velocity.length() > slip_speed:
100 | drifting = true
101 | if drifting and velocity.length() < slip_speed and steer_angle == 0:
102 | drifting = false
103 | var traction = traction_fast if drifting else traction_slow
104 |
105 | direction = new_heading.dot(velocity.normalized())
106 |
107 | if direction > 0: # reverse
108 | velocity = lerp(velocity, new_heading * min(velocity.length(), max_speed_reverse), traction)
109 | if direction < 0: # forward
110 | velocity = lerp(velocity, -new_heading * min(velocity.length(), max_speed_forward), traction)
111 |
112 | look_at(transform.origin + new_heading, transform.basis.y)
113 |
--------------------------------------------------------------------------------
/materials/wall.ptex:
--------------------------------------------------------------------------------
1 | {
2 | "connections": [
3 | {
4 | "from": "beehive",
5 | "from_port": 0,
6 | "to": "colorize",
7 | "to_port": 0
8 | },
9 | {
10 | "from": "colorize",
11 | "from_port": 0,
12 | "to": "Material",
13 | "to_port": 0
14 | },
15 | {
16 | "from": "beehive",
17 | "from_port": 0,
18 | "to": "colorize_2",
19 | "to_port": 0
20 | },
21 | {
22 | "from": "colorize_2",
23 | "from_port": 0,
24 | "to": "normal_map2",
25 | "to_port": 0
26 | },
27 | {
28 | "from": "normal_map2",
29 | "from_port": 0,
30 | "to": "Material",
31 | "to_port": 4
32 | },
33 | {
34 | "from": "colorize_2",
35 | "from_port": 0,
36 | "to": "colorize_3",
37 | "to_port": 0
38 | },
39 | {
40 | "from": "colorize_3",
41 | "from_port": 0,
42 | "to": "Material",
43 | "to_port": 7
44 | }
45 | ],
46 | "label": "Graph",
47 | "longdesc": "",
48 | "name": "@@322",
49 | "node_position": {
50 | "x": 0,
51 | "y": 0
52 | },
53 | "nodes": [
54 | {
55 | "export_last_target": "Godot/Godot 4 Standard",
56 | "export_paths": {
57 | "Godot/Godot 4 Standard": "/home/al/Godot/projects/phys_test/materials/wall"
58 | },
59 | "name": "Material",
60 | "node_position": {
61 | "x": 953,
62 | "y": 194
63 | },
64 | "parameters": {
65 | "albedo_color": {
66 | "a": 0.258824,
67 | "b": 1,
68 | "g": 1,
69 | "r": 1,
70 | "type": "Color"
71 | },
72 | "ao": 1,
73 | "depth_scale": 0.5,
74 | "emission_energy": 1,
75 | "flags_transparent": true,
76 | "metallic": 0,
77 | "normal": 1,
78 | "roughness": 1,
79 | "size": 11,
80 | "sss": 1
81 | },
82 | "seed_int": 0,
83 | "type": "material"
84 | },
85 | {
86 | "name": "beehive",
87 | "node_position": {
88 | "x": -316,
89 | "y": 152
90 | },
91 | "parameters": {
92 | "sx": 8,
93 | "sy": 4
94 | },
95 | "seed_int": 0,
96 | "type": "beehive2"
97 | },
98 | {
99 | "name": "colorize",
100 | "node_position": {
101 | "x": -18,
102 | "y": 163
103 | },
104 | "parameters": {
105 | "gradient": {
106 | "interpolation": 0,
107 | "points": [
108 | {
109 | "a": 1,
110 | "b": 1,
111 | "g": 1,
112 | "pos": 0,
113 | "r": 1
114 | },
115 | {
116 | "a": 0.196078,
117 | "b": 0.686275,
118 | "g": 0.262745,
119 | "pos": 0.154545,
120 | "r": 0.184314
121 | }
122 | ],
123 | "type": "Gradient"
124 | }
125 | },
126 | "seed_int": 0,
127 | "type": "colorize"
128 | },
129 | {
130 | "name": "colorize_2",
131 | "node_position": {
132 | "x": -1,
133 | "y": 282
134 | },
135 | "parameters": {
136 | "gradient": {
137 | "interpolation": 1,
138 | "points": [
139 | {
140 | "a": 1,
141 | "b": 1,
142 | "g": 1,
143 | "pos": 0,
144 | "r": 1
145 | },
146 | {
147 | "a": 1,
148 | "b": 0,
149 | "g": 0,
150 | "pos": 0.154545,
151 | "r": 0
152 | }
153 | ],
154 | "type": "Gradient"
155 | }
156 | },
157 | "seed_int": 0,
158 | "type": "colorize"
159 | },
160 | {
161 | "name": "normal_map2",
162 | "node_position": {
163 | "x": 503.267944,
164 | "y": 462.442596
165 | },
166 | "parameters": {
167 | "buffer": 1,
168 | "param2": 0,
169 | "size": 10,
170 | "strength": 1
171 | },
172 | "seed_int": 0,
173 | "type": "normal_map2"
174 | },
175 | {
176 | "name": "colorize_3",
177 | "node_position": {
178 | "x": 364.863525,
179 | "y": 259.922546
180 | },
181 | "parameters": {
182 | "gradient": {
183 | "interpolation": 1,
184 | "points": [
185 | {
186 | "a": 1,
187 | "b": 0,
188 | "g": 0,
189 | "pos": 0,
190 | "r": 0
191 | },
192 | {
193 | "a": 1,
194 | "b": 0.269531,
195 | "g": 0.269531,
196 | "pos": 1,
197 | "r": 0.269531
198 | }
199 | ],
200 | "type": "Gradient"
201 | }
202 | },
203 | "seed_int": 0,
204 | "type": "colorize"
205 | }
206 | ],
207 | "parameters": {
208 |
209 | },
210 | "seed_int": 0,
211 | "shortdesc": "",
212 | "type": "graph"
213 | }
--------------------------------------------------------------------------------
/scenes/player_rb.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=10 format=3 uid="uid://rbhb1te77y2d"]
2 |
3 | [ext_resource type="Script" uid="uid://lxgmbbyk3nvi" path="res://scripts/player_rb.gd" id="1_35slv"]
4 | [ext_resource type="Script" uid="uid://d350u8evihs1u" path="res://addons/netfox/rollback/rollback-synchronizer.gd" id="2_1k5lo"]
5 | [ext_resource type="PackedScene" uid="uid://dtoa53rtwixhg" path="res://models/car.glb" id="2_athq8"]
6 | [ext_resource type="Script" uid="uid://cbmc5r8tb2ub7" path="res://scripts/input.gd" id="3_3k3na"]
7 | [ext_resource type="PackedScene" uid="uid://d2sn826pqsfs5" path="res://scenes/wheel.tscn" id="4_1k5lo"]
8 | [ext_resource type="Script" uid="uid://dour8fehaaugp" path="res://addons/netfox/tick-interpolator.gd" id="5_1k5lo"]
9 | [ext_resource type="PackedScene" uid="uid://26d7swkp18du" path="res://scenes/car_ai.tscn" id="7_athq8"]
10 |
11 | [sub_resource type="PhysicsMaterial" id="PhysicsMaterial_athq8"]
12 | friction = 0.75
13 | bounce = 0.2
14 |
15 | [sub_resource type="BoxShape3D" id="BoxShape3D_dqkch"]
16 | size = Vector3(1.75, 1, 3)
17 |
18 | [node name="Player" type="RigidBody3D" groups=["players"]]
19 | collision_layer = 3
20 | collision_mask = 3
21 | mass = 20.0
22 | physics_material_override = SubResource("PhysicsMaterial_athq8")
23 | center_of_mass_mode = 1
24 | center_of_mass = Vector3(0, -0.5, 0)
25 | can_sleep = false
26 | continuous_cd = true
27 | linear_damp = 0.3
28 | angular_damp = 1.0
29 | script = ExtResource("1_35slv")
30 | spring_strength = 100.0
31 | engine_power = 200.0
32 | steering_angle = 25.0
33 | front_tire_grip = 1.5
34 | metadata/_custom_type_script = "uid://lxgmbbyk3nvi"
35 |
36 | [node name="Car_model" parent="." instance=ExtResource("2_athq8")]
37 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.0643204, -0.231174)
38 |
39 | [node name="CollisionShape3D" type="CollisionShape3D" parent="."]
40 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
41 | shape = SubResource("BoxShape3D_dqkch")
42 |
43 | [node name="RollbackSynchronizer" type="Node" parent="." node_paths=PackedStringArray("root")]
44 | script = ExtResource("2_1k5lo")
45 | root = NodePath("..")
46 | state_properties = Array[String]([":physics_state", ":previous_spring_lengths", ":team"])
47 | input_properties = Array[String](["Input:motion", "Input:jumping"])
48 |
49 | [node name="Input" type="Node" parent="."]
50 | script = ExtResource("3_3k3na")
51 |
52 | [node name="Label3D" type="Label3D" parent="."]
53 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.8478, 0)
54 | visible = false
55 | billboard = 1
56 | shaded = true
57 | no_depth_test = true
58 | text = "Test
59 | "
60 | font_size = 60
61 |
62 | [node name="Wheels" type="Node3D" parent="."]
63 |
64 | [node name="FL_Wheel" parent="Wheels" instance=ExtResource("4_1k5lo")]
65 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.75, 0.291376, 1)
66 | enabled = false
67 | hit_from_inside = true
68 | metadata/_custom_type_script = "uid://c8tk028y8ex3g"
69 |
70 | [node name="FR_Wheel" parent="Wheels" instance=ExtResource("4_1k5lo")]
71 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.75, 0.291376, 1)
72 | enabled = false
73 | hit_from_inside = true
74 | metadata/_custom_type_script = "uid://c8tk028y8ex3g"
75 |
76 | [node name="RL_Wheel" parent="Wheels" instance=ExtResource("4_1k5lo")]
77 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.75, 0.291376, -1)
78 | enabled = false
79 | hit_from_inside = true
80 | is_front_wheel = false
81 | metadata/_custom_type_script = "uid://c8tk028y8ex3g"
82 |
83 | [node name="RR_Wheel" parent="Wheels" instance=ExtResource("4_1k5lo")]
84 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.75, 0.291376, -1)
85 | enabled = false
86 | hit_from_inside = true
87 | is_front_wheel = false
88 | metadata/_custom_type_script = "uid://c8tk028y8ex3g"
89 |
90 | [node name="TickInterpolator" type="Node" parent="." node_paths=PackedStringArray("root")]
91 | script = ExtResource("5_1k5lo")
92 | root = NodePath("..")
93 | properties = Array[String]([":global_transform"])
94 | metadata/_custom_type_script = "uid://dour8fehaaugp"
95 |
96 | [node name="Car_AI" parent="." instance=ExtResource("7_athq8")]
97 |
98 | [node name="RoofBounce" type="RayCast3D" parent="."]
99 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.9680519, 0)
100 | enabled = false
101 | target_position = Vector3(0, 0.5, 0)
102 |
--------------------------------------------------------------------------------
/scripts/wheel.gd:
--------------------------------------------------------------------------------
1 | extends RayCast3D
2 |
3 | class_name RayWheel
4 |
5 | @onready var car: RigidBody3D = get_parent().get_parent()
6 | @onready var wheel = $Wheel
7 |
8 | var previous_spring_length: float = 0.0
9 |
10 | @export var is_front_wheel: bool
11 |
12 | func _ready():
13 | add_exception(car)
14 |
15 | func wheel_tick(delta, _tick):
16 | force_raycast_update()
17 |
18 | if is_colliding():
19 | var collision_point = get_collision_point()
20 |
21 | suspension(delta, collision_point)
22 | acceleration(collision_point)
23 |
24 | apply_z_force(collision_point)
25 | apply_x_force(delta, collision_point)
26 |
27 | set_wheel_position(to_local(get_collision_point()).y + car.wheel_radius)
28 | rotate_wheel(delta)
29 | else:
30 | set_wheel_position(-car.suspension_rest_dist)
31 |
32 |
33 | func apply_x_force(delta, collision_point):
34 | var dir: Vector3 = global_basis.x
35 | var state := PhysicsServer3D.body_get_direct_state(car.get_rid())
36 | var tire_world_vel := state.get_velocity_at_local_position(global_position - car.global_position)
37 | var lateral_vel: float = dir.dot(tire_world_vel)
38 |
39 | var grip = car.rear_tire_grip
40 |
41 | if is_front_wheel:
42 | grip = car.front_tire_grip
43 |
44 | var desired_vel_change: float = - lateral_vel * grip
45 | var x_force = desired_vel_change / delta
46 |
47 | car.apply_force(dir * x_force, collision_point - car.global_position)
48 |
49 | if car.debug:
50 | DebugDraw3D.draw_arrow(global_position, global_position + (dir * x_force / 20), Color.RED, 0.1, true)
51 |
52 |
53 | func apply_z_force(collision_point):
54 | var dir: Vector3 = global_basis.z
55 | var state := PhysicsServer3D.body_get_direct_state(car.get_rid())
56 | var tire_world_vel := state.get_velocity_at_local_position(global_position - car.global_position)
57 | var z_force = dir.dot(tire_world_vel) * car.mass / 10
58 |
59 | car.apply_force(-dir * z_force, collision_point - car.global_position)
60 |
61 | var point = Vector3(collision_point.x, collision_point.y + car.wheel_radius, collision_point.z)
62 |
63 | if car.debug:
64 | DebugDraw3D.draw_arrow(point, point + (-dir * z_force / 2), Color.BLUE_VIOLET, 0.1, true)
65 |
66 |
67 | func set_wheel_position(new_y_position: float):
68 | wheel.position.y = lerp(wheel.position.y, new_y_position, 0.6)
69 |
70 |
71 | func rotate_wheel(delta: float):
72 | var dir = car.basis.z
73 | var rotation_direction = 1 if car.linear_velocity.dot(dir) > 0 else -1
74 |
75 | wheel.rotate_x(rotation_direction * car.linear_velocity.length() * delta)
76 |
77 |
78 | func acceleration(collision_point):
79 | if is_front_wheel:
80 | return
81 |
82 | var accel_dir = - global_basis.z
83 |
84 | var torque = car.accel_input * car.engine_power
85 |
86 | var point = Vector3(collision_point.x, collision_point.y, collision_point.z)
87 |
88 | car.apply_force(accel_dir * torque, point - car.global_position)
89 |
90 | if car.debug:
91 | DebugDraw3D.draw_arrow(point, point + (accel_dir * torque / 20), Color.BLUE, 0.1, true)
92 |
93 |
94 | func suspension(delta, collision_point):
95 | # the direction the force will be applied
96 | var susp_dir = global_basis.y
97 |
98 | var raycast_origin = global_position
99 | var raycast_dest = collision_point
100 | var distance = raycast_dest.distance_to(raycast_origin)
101 |
102 | var spring_length = clamp(distance - car.wheel_radius, 0, car.suspension_rest_dist)
103 |
104 | var spring_force = car.spring_strength * (car.suspension_rest_dist - spring_length)
105 |
106 | var spring_velocity = (previous_spring_length - spring_length) / delta
107 |
108 | var damper_force = car.spring_damper * spring_velocity
109 |
110 | var suspension_force = basis.y * (spring_force + damper_force)
111 |
112 | previous_spring_length = spring_length
113 |
114 | var point = Vector3(collision_point.x, collision_point.y + car.wheel_radius, collision_point.z)
115 |
116 | car.apply_force(susp_dir * suspension_force, point - car.global_position)
117 |
118 | if car.debug:
119 | DebugDraw3D.draw_arrow(global_position, to_global(position + Vector3(-position.x, (suspension_force.y / 20), -position.z)), Color.GREEN, 0.1, true)
120 | DebugDraw3D.draw_line_hit_offset(global_position, to_global(position + Vector3(-position.x, -1, -position.z)), true, distance, 0.2, Color.RED, Color.RED)
121 |
--------------------------------------------------------------------------------
/addons/netfox.internals/logger.gd:
--------------------------------------------------------------------------------
1 | extends RefCounted
2 | class_name _NetfoxLogger
3 |
4 | enum {
5 | LOG_MIN,
6 | LOG_TRACE,
7 | LOG_DEBUG,
8 | LOG_INFO,
9 | LOG_WARN,
10 | LOG_ERROR,
11 | LOG_MAX
12 | }
13 |
14 | const DEFAULT_LOG_LEVEL := LOG_DEBUG
15 |
16 | static var log_level: int
17 | static var module_log_level: Dictionary
18 |
19 | static var _tags: Dictionary = {}
20 | static var _ordered_tags: Array[Callable] = []
21 |
22 | var module: String
23 | var name: String
24 |
25 | const level_prefixes: Array[String] = [
26 | "",
27 | "TRC",
28 | "DBG",
29 | "INF",
30 | "WRN",
31 | "ERR",
32 | ""
33 | ]
34 |
35 | static func for_netfox(p_name: String) -> _NetfoxLogger:
36 | return _NetfoxLogger.new("netfox", p_name)
37 |
38 | static func for_noray(p_name: String) -> _NetfoxLogger:
39 | return _NetfoxLogger.new("netfox.noray", p_name)
40 |
41 | static func for_extras(p_name: String) -> _NetfoxLogger:
42 | return _NetfoxLogger.new("netfox.extras", p_name)
43 |
44 | static func make_setting(name: String) -> Dictionary:
45 | return {
46 | "name": name,
47 | "value": DEFAULT_LOG_LEVEL,
48 | "type": TYPE_INT,
49 | "hint": PROPERTY_HINT_ENUM,
50 | "hint_string": "All,Trace,Debug,Info,Warning,Error,None"
51 | }
52 |
53 | static func register_tag(tag: Callable, priority: int = 0) -> void:
54 | # Save tag
55 | if not _tags.has(priority):
56 | _tags[priority] = [tag]
57 | else:
58 | _tags[priority].push_back(tag)
59 |
60 | # Recalculate tag order
61 | _ordered_tags.clear()
62 |
63 | var prio_groups = _tags.keys()
64 | prio_groups.sort()
65 |
66 | for prio_group in prio_groups:
67 | var tag_group = _tags[prio_group]
68 | _ordered_tags.append_array(tag_group)
69 |
70 | static func free_tag(tag: Callable) -> void:
71 | for priority in _tags.keys():
72 | var priority_group := _tags[priority] as Array
73 | priority_group.erase(tag)
74 |
75 | # NOTE: Arrays are passed as reference, no need to re-assign after modifying
76 | if priority_group.is_empty():
77 | _tags.erase(priority)
78 |
79 | _ordered_tags.erase(tag)
80 |
81 | static func _static_init():
82 | log_level = ProjectSettings.get_setting(&"netfox/logging/log_level", DEFAULT_LOG_LEVEL)
83 | module_log_level = {
84 | "netfox": ProjectSettings.get_setting(&"netfox/logging/netfox_log_level", DEFAULT_LOG_LEVEL),
85 | "netfox.noray": ProjectSettings.get_setting(&"netfox/logging/netfox_noray_log_level", DEFAULT_LOG_LEVEL),
86 | "netfox.extras": ProjectSettings.get_setting(&"netfox/logging/netfox_extras_log_level", DEFAULT_LOG_LEVEL)
87 | }
88 |
89 | func _init(p_module: String, p_name: String):
90 | module = p_module
91 | name = p_name
92 |
93 | func _check_log_level(level: int) -> bool:
94 | var cmp_level = log_level
95 | if level < cmp_level:
96 | return false
97 |
98 | if module_log_level.has(module):
99 | var module_level = module_log_level.get(module)
100 | return level >= module_level
101 |
102 | return true
103 |
104 | func _format_text(text: String, values: Array, level: int) -> String:
105 | level = clampi(level, LOG_MIN, LOG_MAX)
106 |
107 | var result := PackedStringArray()
108 |
109 | result.append("[%s]" % [level_prefixes[level]])
110 | for tag in _ordered_tags:
111 | result.append("[%s]" % [tag.call()])
112 | result.append("[%s::%s] " % [module, name])
113 |
114 | if values.is_empty():
115 | result.append(text)
116 | else:
117 | result.append(text % values)
118 |
119 | return "".join(result)
120 |
121 | func _log_text(text: String, values: Array, level: int):
122 | if _check_log_level(level):
123 | print(_format_text(text, values, level))
124 |
125 | func trace(text: String, values: Array = []):
126 | _log_text(text, values, LOG_TRACE)
127 |
128 | func debug(text: String, values: Array = []):
129 | _log_text(text, values, LOG_DEBUG)
130 |
131 | func info(text: String, values: Array = []):
132 | _log_text(text, values, LOG_INFO)
133 |
134 | func warning(text: String, values: Array = []):
135 | if _check_log_level(LOG_WARN):
136 | var formatted_text = _format_text(text, values, LOG_WARN)
137 | push_warning(formatted_text)
138 | # Print so it shows up in the Output panel too
139 | print(formatted_text)
140 |
141 | func error(text: String, values: Array = []):
142 | if _check_log_level(LOG_ERROR):
143 | var formatted_text = _format_text(text, values, LOG_ERROR)
144 | push_error(formatted_text)
145 | # Print so it shows up in the Output panel too
146 | print(formatted_text)
147 |
--------------------------------------------------------------------------------