Browse Source

Merge branch 'feature-refactor-lobby' into sync

master
Luna 7 years ago
parent
commit
2588ba8a29
60 changed files with 2852 additions and 765 deletions
  1. +0
    -1
      .gitignore
  2. +6
    -2
      README.md
  3. +19
    -0
      assets/heroes/0.obj.import
  4. +19
    -0
      assets/heroes/0_head.obj.import
  5. +19
    -0
      assets/heroes/1.obj.import
  6. +19
    -0
      assets/heroes/1_head.obj.import
  7. +19
    -0
      assets/heroes/2.obj.import
  8. +19
    -0
      assets/heroes/2_head.obj.import
  9. +19
    -0
      assets/heroes/3.obj.import
  10. +19
    -0
      assets/heroes/3_head.obj.import
  11. +19
    -0
      assets/heroes/4.obj.import
  12. +19
    -0
      assets/heroes/4_beam.obj.import
  13. +19
    -0
      assets/heroes/4_head.obj.import
  14. +19
    -0
      assets/heroes/5.obj.import
  15. +19
    -0
      assets/heroes/5_head.obj.import
  16. +19
    -0
      assets/heroes/5_portal.obj.import
  17. +19
    -0
      assets/levels/0.obj.import
  18. +19
    -0
      assets/levels/1.obj.import
  19. +1065
    -0
      assets/levels/2.dae.import
  20. +19
    -0
      assets/levels/2.obj.import
  21. +19
    -0
      assets/maze-high-obj.obj.import
  22. +19
    -0
      assets/maze.obj.import
  23. +32
    -0
      assets/objective-left.png.import
  24. +32
    -0
      assets/objective-right.png.import
  25. +19
    -0
      assets/objective.obj.import
  26. +19
    -0
      assets/player.obj.import
  27. +16
    -0
      assets/theme.tres
  28. +32
    -0
      docs/2018-02-14-heroes.png.import
  29. +32
    -0
      icon.png.import
  30. +3
    -2
      plans.md
  31. +2
    -1
      project.godot
  32. +38
    -17
      scenes/ability_icon.tscn
  33. +232
    -0
      scenes/custom_game.tscn
  34. +2
    -1
      scenes/heroes/0.tscn
  35. +1
    -0
      scenes/heroes/4.tscn
  36. +2
    -1
      scenes/heroes/5.tscn
  37. +0
    -1
      scenes/level.tscn
  38. +119
    -465
      scenes/lobby.tscn
  39. +166
    -0
      scenes/menu.tscn
  40. +20
    -5
      scenes/player.tscn
  41. +82
    -0
      scenes/singleplayer_lobby.tscn
  42. +23
    -0
      scripts/ability_icon.gd
  43. +14
    -0
      scripts/custom_game.gd
  44. +11
    -0
      scripts/hero_select.gd
  45. +1
    -1
      scripts/heroes/0.gd
  46. +1
    -1
      scripts/heroes/1_wall.gd
  47. +1
    -1
      scripts/heroes/2.gd
  48. +2
    -2
      scripts/heroes/3.gd
  49. +3
    -3
      scripts/heroes/4.gd
  50. +2
    -2
      scripts/heroes/5.gd
  51. +4
    -1
      scripts/heroes/5_portal.gd
  52. +92
    -249
      scripts/lobby.gd
  53. +105
    -0
      scripts/matchmaking.gd
  54. +56
    -0
      scripts/menu.gd
  55. +193
    -0
      scripts/networking.gd
  56. +27
    -6
      scripts/player.gd
  57. +33
    -1
      scripts/util.gd
  58. +1
    -0
      util/matchmaker.sh
  59. +1
    -1
      util/server.sh
  60. +1
    -1
      util/start-multiple.sh

+ 0
- 1
.gitignore View File

@ -3,7 +3,6 @@
*.swo
.fscache
.import
*.import
*.blend1
nohup.out
recordings


+ 6
- 2
README.md View File

@ -27,14 +27,17 @@ Current heroes:
- CARITAS (Margarine)
- SUPERBIA (Build portals)
Ideas for Heroes:
Ideas for Heroes / Abilities:
- More active
- Blink (Tracer) - no cooldown, but loses all speed on hitting walls
- Blink (like tracer) costs charge
- Heavy guy - Slow, but very heavy for the see-saw
- Climb and glide abilities
- JUMPING
- More supportive
- Switch places with a TEAMMATE
- This is awesomely self-regulating because it's zero-sum
- However, it can't really gain charge because it can still be abused
- Boost - Area of effect or zarya-like cast, speeds people up
- Flop - Changes side of see-saw for each team (should it be mechanic instead??)
- Build - Build Zineth-like rails for anyone to use
@ -42,6 +45,7 @@ Ideas for Heroes:
- (Combine with LUSSURIA?) Hook and swing on terrain
- More offensive
- Lay traps
- Freeze a hero. A teammate of that hero must unfreeze them! (Or *n* seconds or whatever)
- (Combine with PAZIENZA?) Destroy buildings
- (Combine wit LUSSURIA?) Hold, then release to explode enemies away


+ 19
- 0
assets/heroes/0.obj.import View File

@ -0,0 +1,19 @@
[remap]
importer="wavefront_obj"
type="Mesh"
path="res://.import/0.obj-5ff3661b08325671ce64079449bc78dd.mesh"
[deps]
files=[ "res://.import/0.obj-5ff3661b08325671ce64079449bc78dd.mesh" ]
source_file="res://assets/heroes/0.obj"
source_md5="214f8b376199d3c947df1d85a0928dec"
dest_files=[ "res://.import/0.obj-5ff3661b08325671ce64079449bc78dd.mesh", "res://.import/0.obj-5ff3661b08325671ce64079449bc78dd.mesh" ]
dest_md5="4306afcb3a4b4978cce778634c97e7d0"
[params]
generate_tangents=true

+ 19
- 0
assets/heroes/0_head.obj.import View File

@ -0,0 +1,19 @@
[remap]
importer="wavefront_obj"
type="Mesh"
path="res://.import/0_head.obj-0b002be4b283361f171e2d4dd9a85105.mesh"
[deps]
files=[ "res://.import/0_head.obj-0b002be4b283361f171e2d4dd9a85105.mesh" ]
source_file="res://assets/heroes/0_head.obj"
source_md5="4ae67462b018895ed3f258a0db4239e4"
dest_files=[ "res://.import/0_head.obj-0b002be4b283361f171e2d4dd9a85105.mesh", "res://.import/0_head.obj-0b002be4b283361f171e2d4dd9a85105.mesh" ]
dest_md5="7094164b68aeac1c4dd9bd292282e4b9"
[params]
generate_tangents=true

+ 19
- 0
assets/heroes/1.obj.import View File

@ -0,0 +1,19 @@
[remap]
importer="wavefront_obj"
type="Mesh"
path="res://.import/1.obj-3a9ae1e4a3d92228bd05741755fdbcdd.mesh"
[deps]
files=[ "res://.import/1.obj-3a9ae1e4a3d92228bd05741755fdbcdd.mesh" ]
source_file="res://assets/heroes/1.obj"
source_md5="6a83c78e54b84bd01eb44630c48362f2"
dest_files=[ "res://.import/1.obj-3a9ae1e4a3d92228bd05741755fdbcdd.mesh", "res://.import/1.obj-3a9ae1e4a3d92228bd05741755fdbcdd.mesh" ]
dest_md5="808804a04eb10327391dd96c30f14435"
[params]
generate_tangents=true

+ 19
- 0
assets/heroes/1_head.obj.import View File

@ -0,0 +1,19 @@
[remap]
importer="wavefront_obj"
type="Mesh"
path="res://.import/1_head.obj-661a3a390ef1299857e3de1aba5a1d01.mesh"
[deps]
files=[ "res://.import/1_head.obj-661a3a390ef1299857e3de1aba5a1d01.mesh" ]
source_file="res://assets/heroes/1_head.obj"
source_md5="78a4e4475d699dbd061ec9a916cbd606"
dest_files=[ "res://.import/1_head.obj-661a3a390ef1299857e3de1aba5a1d01.mesh", "res://.import/1_head.obj-661a3a390ef1299857e3de1aba5a1d01.mesh" ]
dest_md5="65c8304300dd9929d0b3b008623845a0"
[params]
generate_tangents=true

+ 19
- 0
assets/heroes/2.obj.import View File

@ -0,0 +1,19 @@
[remap]
importer="wavefront_obj"
type="Mesh"
path="res://.import/2.obj-85c8b4ac312b9585e4dda4f545679e80.mesh"
[deps]
files=[ "res://.import/2.obj-85c8b4ac312b9585e4dda4f545679e80.mesh" ]
source_file="res://assets/heroes/2.obj"
source_md5="7b2a64047a6477edd16b10f1c8601e15"
dest_files=[ "res://.import/2.obj-85c8b4ac312b9585e4dda4f545679e80.mesh", "res://.import/2.obj-85c8b4ac312b9585e4dda4f545679e80.mesh" ]
dest_md5="cf97b53977d069c0f48e104093031357"
[params]
generate_tangents=true

+ 19
- 0
assets/heroes/2_head.obj.import View File

@ -0,0 +1,19 @@
[remap]
importer="wavefront_obj"
type="Mesh"
path="res://.import/2_head.obj-642d7e5721ba3621d4aeef33bf9079f3.mesh"
[deps]
files=[ "res://.import/2_head.obj-642d7e5721ba3621d4aeef33bf9079f3.mesh" ]
source_file="res://assets/heroes/2_head.obj"
source_md5="3800b7ca51634172c29e2627d0688739"
dest_files=[ "res://.import/2_head.obj-642d7e5721ba3621d4aeef33bf9079f3.mesh", "res://.import/2_head.obj-642d7e5721ba3621d4aeef33bf9079f3.mesh" ]
dest_md5="f41bf13cd6278973e335edcb5af07800"
[params]
generate_tangents=true

+ 19
- 0
assets/heroes/3.obj.import View File

@ -0,0 +1,19 @@
[remap]
importer="wavefront_obj"
type="Mesh"
path="res://.import/3.obj-a2b36e0242832fc3ee6cf8133bec1df6.mesh"
[deps]
files=[ "res://.import/3.obj-a2b36e0242832fc3ee6cf8133bec1df6.mesh" ]
source_file="res://assets/heroes/3.obj"
source_md5="ae31181eed13485b325d439fd465e246"
dest_files=[ "res://.import/3.obj-a2b36e0242832fc3ee6cf8133bec1df6.mesh", "res://.import/3.obj-a2b36e0242832fc3ee6cf8133bec1df6.mesh" ]
dest_md5="3afa20d19f1fa09fcf2fee691c245317"
[params]
generate_tangents=true

+ 19
- 0
assets/heroes/3_head.obj.import View File

@ -0,0 +1,19 @@
[remap]
importer="wavefront_obj"
type="Mesh"
path="res://.import/3_head.obj-31149354fd38179ee6897017177772af.mesh"
[deps]
files=[ "res://.import/3_head.obj-31149354fd38179ee6897017177772af.mesh" ]
source_file="res://assets/heroes/3_head.obj"
source_md5="4f6b31cfbe16a55cce978896cf8b0b12"
dest_files=[ "res://.import/3_head.obj-31149354fd38179ee6897017177772af.mesh", "res://.import/3_head.obj-31149354fd38179ee6897017177772af.mesh" ]
dest_md5="8ba28a8f98e213a2382b508bb344084c"
[params]
generate_tangents=true

+ 19
- 0
assets/heroes/4.obj.import View File

@ -0,0 +1,19 @@
[remap]
importer="wavefront_obj"
type="Mesh"
path="res://.import/4.obj-773905e6e7c2c08ad34fe36bfaacca4e.mesh"
[deps]
files=[ "res://.import/4.obj-773905e6e7c2c08ad34fe36bfaacca4e.mesh" ]
source_file="res://assets/heroes/4.obj"
source_md5="8a2ea58bae6a5ba0b5a28b7ab6163ed9"
dest_files=[ "res://.import/4.obj-773905e6e7c2c08ad34fe36bfaacca4e.mesh", "res://.import/4.obj-773905e6e7c2c08ad34fe36bfaacca4e.mesh" ]
dest_md5="93358c6c816510bd069825e089cee878"
[params]
generate_tangents=true

+ 19
- 0
assets/heroes/4_beam.obj.import View File

@ -0,0 +1,19 @@
[remap]
importer="wavefront_obj"
type="Mesh"
path="res://.import/4_beam.obj-00b64391bae3ea00b0d3ef163bfa8860.mesh"
[deps]
files=[ "res://.import/4_beam.obj-00b64391bae3ea00b0d3ef163bfa8860.mesh" ]
source_file="res://assets/heroes/4_beam.obj"
source_md5="8583eb948f98e108f6848c4fc96a2562"
dest_files=[ "res://.import/4_beam.obj-00b64391bae3ea00b0d3ef163bfa8860.mesh", "res://.import/4_beam.obj-00b64391bae3ea00b0d3ef163bfa8860.mesh" ]
dest_md5="76cbfe93be8dd02d9519c5284d1e638f"
[params]
generate_tangents=true

+ 19
- 0
assets/heroes/4_head.obj.import View File

@ -0,0 +1,19 @@
[remap]
importer="wavefront_obj"
type="Mesh"
path="res://.import/4_head.obj-0f05fd2c5005558302023bc511be8ae6.mesh"
[deps]
files=[ "res://.import/4_head.obj-0f05fd2c5005558302023bc511be8ae6.mesh" ]
source_file="res://assets/heroes/4_head.obj"
source_md5="2ed548efe830daac45b9e4fa4b5b1b73"
dest_files=[ "res://.import/4_head.obj-0f05fd2c5005558302023bc511be8ae6.mesh", "res://.import/4_head.obj-0f05fd2c5005558302023bc511be8ae6.mesh" ]
dest_md5="9a188b6c84c8a296472464a3366650fc"
[params]
generate_tangents=true

+ 19
- 0
assets/heroes/5.obj.import View File

@ -0,0 +1,19 @@
[remap]
importer="wavefront_obj"
type="Mesh"
path="res://.import/5.obj-aae555f5fa1f112fdc160f94735bf9d5.mesh"
[deps]
files=[ "res://.import/5.obj-aae555f5fa1f112fdc160f94735bf9d5.mesh" ]
source_file="res://assets/heroes/5.obj"
source_md5="5a020906bcd263a5302ebe5074b43c01"
dest_files=[ "res://.import/5.obj-aae555f5fa1f112fdc160f94735bf9d5.mesh", "res://.import/5.obj-aae555f5fa1f112fdc160f94735bf9d5.mesh" ]
dest_md5="bb5b4b3ca681eb185284560d7f4a8aed"
[params]
generate_tangents=true

+ 19
- 0
assets/heroes/5_head.obj.import View File

@ -0,0 +1,19 @@
[remap]
importer="wavefront_obj"
type="Mesh"
path="res://.import/5_head.obj-99b3a2eaf8ae9e841b6fdcc8d4884ea7.mesh"
[deps]
files=[ "res://.import/5_head.obj-99b3a2eaf8ae9e841b6fdcc8d4884ea7.mesh" ]
source_file="res://assets/heroes/5_head.obj"
source_md5="453157b7ed836fe44c392ad6dda3ff67"
dest_files=[ "res://.import/5_head.obj-99b3a2eaf8ae9e841b6fdcc8d4884ea7.mesh", "res://.import/5_head.obj-99b3a2eaf8ae9e841b6fdcc8d4884ea7.mesh" ]
dest_md5="a2bc2ff9d1a92c3be2c8477eceb08768"
[params]
generate_tangents=true

+ 19
- 0
assets/heroes/5_portal.obj.import View File

@ -0,0 +1,19 @@
[remap]
importer="wavefront_obj"
type="Mesh"
path="res://.import/5_portal.obj-a337ed5122c40b180597dd9794ecfe6d.mesh"
[deps]
files=[ "res://.import/5_portal.obj-a337ed5122c40b180597dd9794ecfe6d.mesh" ]
source_file="res://assets/heroes/5_portal.obj"
source_md5="bc944713e3a053455322914a39987891"
dest_files=[ "res://.import/5_portal.obj-a337ed5122c40b180597dd9794ecfe6d.mesh", "res://.import/5_portal.obj-a337ed5122c40b180597dd9794ecfe6d.mesh" ]
dest_md5="b63a25154e6cdd3e7bcb1a3fe4e0e04a"
[params]
generate_tangents=true

+ 19
- 0
assets/levels/0.obj.import View File

@ -0,0 +1,19 @@
[remap]
importer="wavefront_obj"
type="Mesh"
path="res://.import/0.obj-75904c05129c42580cc5d5c2ce0f88f4.mesh"
[deps]
files=[ "res://.import/0.obj-75904c05129c42580cc5d5c2ce0f88f4.mesh" ]
source_file="res://assets/levels/0.obj"
source_md5="c43e5185ee65f03ac94dc947611d85d5"
dest_files=[ "res://.import/0.obj-75904c05129c42580cc5d5c2ce0f88f4.mesh", "res://.import/0.obj-75904c05129c42580cc5d5c2ce0f88f4.mesh" ]
dest_md5="23c015582e544d38c3302925e86550ca"
[params]
generate_tangents=true

+ 19
- 0
assets/levels/1.obj.import View File

@ -0,0 +1,19 @@
[remap]
importer="wavefront_obj"
type="Mesh"
path="res://.import/1.obj-8c0967187ed7a9d970c8667286d96ec8.mesh"
[deps]
files=[ "res://.import/1.obj-8c0967187ed7a9d970c8667286d96ec8.mesh" ]
source_file="res://assets/levels/1.obj"
source_md5="c76ce309215e4ab659d00d70f85051c1"
dest_files=[ "res://.import/1.obj-8c0967187ed7a9d970c8667286d96ec8.mesh", "res://.import/1.obj-8c0967187ed7a9d970c8667286d96ec8.mesh" ]
dest_md5="772c79044dd30ef6a01e2c1b99e3f78c"
[params]
generate_tangents=true

+ 1065
- 0
assets/levels/2.dae.import
File diff suppressed because it is too large
View File


+ 19
- 0
assets/levels/2.obj.import View File

@ -0,0 +1,19 @@
[remap]
importer="wavefront_obj"
type="Mesh"
path="res://.import/2.obj-3a5433501168477746c24182726a90c2.mesh"
[deps]
files=[ "res://.import/2.obj-3a5433501168477746c24182726a90c2.mesh" ]
source_file="res://assets/levels/2.obj"
source_md5="9c1795f0c017f636969954e503a75157"
dest_files=[ "res://.import/2.obj-3a5433501168477746c24182726a90c2.mesh", "res://.import/2.obj-3a5433501168477746c24182726a90c2.mesh" ]
dest_md5="557048f3ebad19044a3c342d9c06e870"
[params]
generate_tangents=true

+ 19
- 0
assets/maze-high-obj.obj.import View File

@ -0,0 +1,19 @@
[remap]
importer="wavefront_obj"
type="Mesh"
path="res://.import/maze-high-obj.obj-24db734e0c48b4dd09ff2d9426ff3915.mesh"
[deps]
files=[ "res://.import/maze-high-obj.obj-24db734e0c48b4dd09ff2d9426ff3915.mesh" ]
source_file="res://assets/maze-high-obj.obj"
source_md5="6957a6487d30265864dc3d40baf411a2"
dest_files=[ "res://.import/maze-high-obj.obj-24db734e0c48b4dd09ff2d9426ff3915.mesh", "res://.import/maze-high-obj.obj-24db734e0c48b4dd09ff2d9426ff3915.mesh" ]
dest_md5="ce1017fcf90646333644720dd66b599f"
[params]
generate_tangents=true

+ 19
- 0
assets/maze.obj.import View File

@ -0,0 +1,19 @@
[remap]
importer="wavefront_obj"
type="Mesh"
path="res://.import/maze.obj-70de96fca66f7b42f2e7948110e529ab.mesh"
[deps]
files=[ "res://.import/maze.obj-70de96fca66f7b42f2e7948110e529ab.mesh" ]
source_file="res://assets/maze.obj"
source_md5="f35f5a0e67714a456ed725761a30cd8e"
dest_files=[ "res://.import/maze.obj-70de96fca66f7b42f2e7948110e529ab.mesh", "res://.import/maze.obj-70de96fca66f7b42f2e7948110e529ab.mesh" ]
dest_md5="611ce2fc213387da9af0b853967a2f25"
[params]
generate_tangents=true

+ 32
- 0
assets/objective-left.png.import View File

@ -0,0 +1,32 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/objective-left.png-587402b851e8944e16eb289e1b058974.stex"
[deps]
source_file="res://assets/objective-left.png"
source_md5="c172ead10f0d4193e46622fe62668f31"
dest_files=[ "res://.import/objective-left.png-587402b851e8944e16eb289e1b058974.stex" ]
dest_md5="09403856c115d79b27429585b5a7b0f5"
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

+ 32
- 0
assets/objective-right.png.import View File

@ -0,0 +1,32 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/objective-right.png-d18195ff55e7aff72b594ff190053ce4.stex"
[deps]
source_file="res://assets/objective-right.png"
source_md5="2725f8f335a4f2ceb2562ef44343830d"
dest_files=[ "res://.import/objective-right.png-d18195ff55e7aff72b594ff190053ce4.stex" ]
dest_md5="c3fa2e979e2fdea8132655a82686526d"
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

+ 19
- 0
assets/objective.obj.import View File

@ -0,0 +1,19 @@
[remap]
importer="wavefront_obj"
type="Mesh"
path="res://.import/objective.obj-d3a42ffdae3ada4606dd452051061b51.mesh"
[deps]
files=[ "res://.import/objective.obj-d3a42ffdae3ada4606dd452051061b51.mesh" ]
source_file="res://assets/objective.obj"
source_md5="a00bec3114f9259f2ae795c28f75e9c9"
dest_files=[ "res://.import/objective.obj-d3a42ffdae3ada4606dd452051061b51.mesh", "res://.import/objective.obj-d3a42ffdae3ada4606dd452051061b51.mesh" ]
dest_md5="56ab97d8a864679c68c7c7d9c1d38dd8"
[params]
generate_tangents=true

+ 19
- 0
assets/player.obj.import View File

@ -0,0 +1,19 @@
[remap]
importer="wavefront_obj"
type="Mesh"
path="res://.import/player.obj-9552f61e8059d9a4dd660ae96ad6a512.mesh"
[deps]
files=[ "res://.import/player.obj-9552f61e8059d9a4dd660ae96ad6a512.mesh" ]
source_file="res://assets/player.obj"
source_md5="7074ae8221d3b083f4f95e70e1ee446f"
dest_files=[ "res://.import/player.obj-9552f61e8059d9a4dd660ae96ad6a512.mesh", "res://.import/player.obj-9552f61e8059d9a4dd660ae96ad6a512.mesh" ]
dest_md5="0ca70d88338dbaa80a9676388d516d63"
[params]
generate_tangents=true

+ 16
- 0
assets/theme.tres View File

@ -0,0 +1,16 @@
[gd_resource type="Theme" load_steps=3 format=2]
[ext_resource path="res://assets/DejaVuSansMono.ttf" type="DynamicFontData" id=1]
[sub_resource type="DynamicFont" id=1]
size = 18
use_mipmaps = false
use_filter = false
font_data = ExtResource( 1 )
_sections_unfolded = [ "Font", "Settings" ]
[resource]
default_font = SubResource( 1 )

+ 32
- 0
docs/2018-02-14-heroes.png.import View File

@ -0,0 +1,32 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/2018-02-14-heroes.png-66ce877f0c90a512065fa1c503625ab1.stex"
[deps]
source_file="res://docs/2018-02-14-heroes.png"
source_md5="98144251e8505d9c53c0ba6cf451448e"
dest_files=[ "res://.import/2018-02-14-heroes.png-66ce877f0c90a512065fa1c503625ab1.stex" ]
dest_md5="b9c1300d34876a596a8e284f821d7bc9"
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

+ 32
- 0
icon.png.import View File

@ -0,0 +1,32 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex"
[deps]
source_file="res://icon.png"
source_md5="7febcf604d750bd224f744362be04a8a"
dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ]
dest_md5="e86d0a8edb5d32d9447a249948fdcf57"
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

+ 3
- 2
plans.md View File

@ -8,6 +8,7 @@ Big TODOs:
- ^ I think you need more abilities for heroes, that combine well
- Like, you could combine PAZIENZA with [destroys buildings]
- Start making maps and figuring out what works / what you like
- Make just one new main level with the lessons you've learned from this one
- I'd love to do a payload map!
- More platforming for everyone (It's industria only rn tbh)
- Give portals a tilted wall to fuck with acceleration
@ -25,8 +26,8 @@ Smaller TODOs:
- Ira is OP?
- Nerfed - 5 walls
- Make motion more reactive?
- Grab someone and draw an impulse on them
- Combine with SUPERBIA, use charge to build portals
- Remember some player settings, probably in a player.json or something
- I'm mostly thinking of nickname, but also SR eventually
Bugs:


+ 2
- 1
project.godot View File

@ -11,12 +11,13 @@ config_version=3
[application]
config/name="nv-moba"
run/main_scene="res://scenes/lobby.tscn"
run/main_scene="res://scenes/menu.tscn"
config/icon="res://icon.png"
[autoload]
util="*res://scripts/util.gd"
networking="*res://scripts/networking.gd"
[input]


+ 38
- 17
scenes/ability_icon.tscn View File

@ -8,7 +8,7 @@ content_margin_left = -1.0
content_margin_right = -1.0
content_margin_top = -1.0
content_margin_bottom = -1.0
bg_color = Color( 0.0297089, 0.230469, 0.206942, 1 )
bg_color = Color( 0.042572, 0.351562, 0.315353, 1 )
draw_center = true
border_width_left = 0
border_width_top = 0
@ -64,10 +64,8 @@ anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = -6.0
margin_top = -4.0
margin_right = 5.0
margin_bottom = 5.0
margin_right = 30.0
margin_bottom = 30.0
rect_pivot_offset = Vector2( 0, 0 )
mouse_filter = 0
mouse_default_cursor_shape = 0
@ -77,6 +75,7 @@ script = ExtResource( 1 )
cost = 1
ability_name = "Ability"
display_progress = true
action = ""
[node name="Bar" type="ProgressBar" parent="." index="0"]
@ -84,10 +83,10 @@ anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = -20.0
margin_top = -21.0
margin_right = 30.0
margin_bottom = 28.0
margin_left = -25.0
margin_top = -25.0
margin_right = 25.0
margin_bottom = 25.0
rect_pivot_offset = Vector2( 0, 0 )
mouse_filter = 0
mouse_default_cursor_shape = 0
@ -110,10 +109,10 @@ anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = -25.0
margin_top = -26.0
margin_right = 25.0
margin_bottom = 23.0
margin_left = -30.0
margin_top = -30.0
margin_right = 20.0
margin_bottom = 20.0
rect_pivot_offset = Vector2( 0, 0 )
mouse_filter = 0
mouse_default_cursor_shape = 0
@ -127,10 +126,10 @@ anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = -65.0
margin_top = 36.0
margin_right = 77.0
margin_bottom = 50.0
margin_left = -72.0
margin_top = -44.0
margin_right = 70.0
margin_bottom = -30.0
rect_pivot_offset = Vector2( 0, 0 )
mouse_filter = 2
mouse_default_cursor_shape = 0
@ -143,4 +142,26 @@ percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="Button" type="Label" parent="." index="3"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = -72.0
margin_top = 29.0
margin_right = 70.0
margin_bottom = 43.0
rect_pivot_offset = Vector2( 0, 0 )
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 4
custom_colors/font_color = Color( 0.00357056, 0.0703125, 0.04372, 1 )
text = "<Button>"
align = 1
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1

+ 232
- 0
scenes/custom_game.tscn View File

@ -0,0 +1,232 @@
[gd_scene load_steps=5 format=2]
[ext_resource path="res://assets/theme.tres" type="Theme" id=1]
[ext_resource path="res://scripts/custom_game.gd" type="Script" id=2]
[ext_resource path="res://assets/DejaVuSansMono.ttf" type="DynamicFontData" id=3]
[sub_resource type="DynamicFont" id=1]
size = 30
use_mipmaps = false
use_filter = false
font_data = ExtResource( 3 )
[node name="CustomGame" type="Control"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_right = 40.0
margin_bottom = 40.0
rect_pivot_offset = Vector2( 0, 0 )
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
theme = ExtResource( 1 )
script = ExtResource( 2 )
[node name="VSeparator" type="VSeparator" parent="." index="0"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 498.0
margin_top = 140.0
margin_right = 518.0
margin_bottom = 481.0
rect_pivot_offset = Vector2( 0, 0 )
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
[node name="Label" type="Label" parent="." index="1"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 54.0
margin_top = 66.0
margin_right = 395.0
margin_bottom = 102.0
rect_pivot_offset = Vector2( 0, 0 )
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 4
custom_fonts/font = SubResource( 1 )
text = "Custom Game"
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="Server" type="Button" parent="." index="2"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 73.0
margin_top = 195.0
margin_right = 366.0
margin_bottom = 252.0
rect_pivot_offset = Vector2( 0, 0 )
focus_mode = 2
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
toggle_mode = false
enabled_focus_mode = 2
shortcut = null
group = null
text = "Host Game"
flat = false
align = 1
[node name="Client" type="Button" parent="." index="3"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 588.0
margin_top = 258.0
margin_right = 890.0
margin_bottom = 315.0
rect_pivot_offset = Vector2( 0, 0 )
focus_mode = 2
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
toggle_mode = false
enabled_focus_mode = 2
shortcut = null
group = null
text = "Join Game"
flat = false
align = 1
[node name="IPLabel" type="Label" parent="." index="4"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 592.0
margin_top = 205.0
margin_right = 625.0
margin_bottom = 227.0
rect_pivot_offset = Vector2( 0, 0 )
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 4
text = "IP:"
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="IP" type="TextEdit" parent="." index="5"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 632.0
margin_top = 203.0
margin_right = 891.0
margin_bottom = 232.0
rect_pivot_offset = Vector2( 0, 0 )
focus_mode = 2
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
text = "127.0.0.1"
readonly = false
highlight_current_line = false
syntax_highlighting = false
show_line_numbers = false
highlight_all_occurrences = false
override_selected_font_color = false
context_menu_enabled = true
smooth_scrolling = false
v_scroll_speed = 80.0
hiding_enabled = 0
wrap_lines = false
caret_block_mode = false
caret_blink = false
caret_blink_speed = 0.65
caret_moving_by_right_click = true
[node name="Label2" type="Label" parent="." index="6"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 72.0
margin_top = 145.0
margin_right = 171.0
margin_bottom = 167.0
rect_pivot_offset = Vector2( 0, 0 )
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 4
text = "Host game"
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="Label3" type="Label" parent="." index="7"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 577.0
margin_top = 147.0
margin_right = 617.0
margin_bottom = 169.0
rect_pivot_offset = Vector2( 0, 0 )
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 4
text = "Join Game"
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="Back" type="Button" parent="." index="8"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 437.0
margin_top = 509.0
margin_right = 581.0
margin_bottom = 537.0
rect_pivot_offset = Vector2( 0, 0 )
focus_mode = 2
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
toggle_mode = false
enabled_focus_mode = 2
shortcut = null
group = null
text = "Back to menu"
flat = false
align = 1

+ 2
- 1
scenes/heroes/0.tscn View File

@ -24,7 +24,7 @@ mesh = ExtResource( 4 )
text = ""
[node name="ProgressBar" parent="MasterOnly" index="1" instance=ExtResource( 5 )]
[node name="Boost" parent="MasterOnly" index="1" instance=ExtResource( 5 )]
anchor_left = 1.0
anchor_top = 1.0
@ -37,5 +37,6 @@ margin_bottom = -109.0
cost = 2
ability_name = "Speed Boost"
display_progress = false
action = "hero_0_boost"

+ 1
- 0
scenes/heroes/4.tscn View File

@ -47,5 +47,6 @@ margin_right = -98.0
margin_bottom = -91.0
cost = 0
ability_name = "Destroy"
action = "primary_ability"

+ 2
- 1
scenes/heroes/5.tscn View File

@ -30,8 +30,9 @@ margin_left = -160.0
margin_top = -112.0
margin_right = -149.0
margin_bottom = -103.0
cost = 20
cost = 75
ability_name = "Build Portal"
action = "hero_5_place_portal"
[node name="Teleport" parent="MasterOnly" index="3" instance=ExtResource( 5 )]


+ 0
- 1
scenes/level.tscn View File

@ -155,7 +155,6 @@ directional_shadow_max_distance = 200.0
[node name="Players" type="Spatial" parent="." index="1"]
[node name="Ball" type="RigidBody" parent="." index="2"]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -14.4344, 6.09969, 4.40885 )


+ 119
- 465
scenes/lobby.tscn View File

@ -1,29 +1,27 @@
[gd_scene load_steps=7 format=2]
[gd_scene load_steps=8 format=2]
[ext_resource path="res://scripts/lobby.gd" type="Script" id=1]
[ext_resource path="res://scripts/hero_select.gd" type="Script" id=2]
[ext_resource path="res://assets/theme.tres" type="Theme" id=1]
[ext_resource path="res://scripts/lobby.gd" type="Script" id=2]
[ext_resource path="res://scenes/hero_select.tscn" type="PackedScene" id=3]
[ext_resource path="res://assets/DejaVuSansMono.ttf" type="DynamicFontData" id=4]
[sub_resource type="DynamicFontData" id=1]
font_path = "res://assets/DejaVuSansMono.ttf"
[sub_resource type="DynamicFont" id=2]
[sub_resource type="DynamicFont" id=1]
size = 30
use_mipmaps = false
use_filter = false
font_data = SubResource( 1 )
font_data = ExtResource( 4 )
[sub_resource type="DynamicFontData" id=3]
[sub_resource type="DynamicFontData" id=2]
font_path = "res://assets/DejaVuSansMono.ttf"
[sub_resource type="DynamicFont" id=4]
[sub_resource type="DynamicFont" id=3]
size = 16
use_mipmaps = false
use_filter = false
font_data = SubResource( 3 )
font_data = SubResource( 2 )
[node name="Lobby" type="Control"]
@ -31,45 +29,6 @@ anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 80.0
margin_top = 99.0
margin_right = 120.0
margin_bottom = 139.0
rect_pivot_offset = Vector2( 0, 0 )
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
script = ExtResource( 1 )
[node name="Title" type="Label" parent="." index="0"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 7.0
margin_top = -72.0
margin_right = 195.0
margin_bottom = -32.0
rect_pivot_offset = Vector2( 0, 0 )
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 4
custom_fonts/font = SubResource( 2 )
text = "VANAGLORIA"
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="GameBrowser" type="Control" parent="." index="1"]
editor/display_folded = true
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_right = 40.0
margin_bottom = 40.0
rect_pivot_offset = Vector2( 0, 0 )
@ -77,158 +36,67 @@ mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
theme = ExtResource( 1 )
script = ExtResource( 2 )
[node name="Play" type="Button" parent="GameBrowser" index="0"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 9.0
margin_top = -2.0
margin_right = 405.0
margin_bottom = 53.0
rect_pivot_offset = Vector2( 0, 0 )
focus_mode = 2
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
toggle_mode = false
enabled_focus_mode = 2
shortcut = null
group = null
text = "Just, Play!"
flat = false
align = 1
[node name="PickAGameLabel" type="Label" parent="GameBrowser" index="1"]
[node name="HeroSelect" parent="." index="0" instance=ExtResource( 3 )]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 10.0
margin_top = 65.0
margin_right = 114.0
margin_bottom = 79.0
rect_pivot_offset = Vector2( 0, 0 )
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 4
text = "Or, pick a game:"
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
margin_left = 30.0
margin_top = 69.0
margin_right = 30.0
margin_bottom = 69.0
color = Color( 0.097229, 0.104696, 0.105469, 0 )
[node name="GameList" type="ItemList" parent="GameBrowser" index="2"]
[node name="Hero" parent="HeroSelect" index="0"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 8.0
margin_top = 87.0
margin_right = 411.0
margin_bottom = 208.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = true
focus_mode = 2
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
items = [ "No Games Running", null, false ]
select_mode = 0
icon_mode = 1
fixed_icon_size = Vector2( 0, 0 )
margin_left = -2.0
margin_top = 41.0
margin_right = 370.0
margin_bottom = 108.0
[node name="Join" type="Button" parent="GameBrowser" index="3"]
[node name="Confirm" parent="HeroSelect" index="1"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 9.0
margin_top = 217.0
margin_right = 163.0
margin_bottom = 244.0
rect_pivot_offset = Vector2( 0, 0 )
focus_mode = 2
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
toggle_mode = false
enabled_focus_mode = 2
shortcut = null
group = null
text = "Join Selected Game"
flat = false
align = 1
[node name="VSeparator" type="VSeparator" parent="." index="2"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 431.0
margin_top = -3.0
margin_right = 436.0
margin_bottom = 234.0
rect_pivot_offset = Vector2( 0, 0 )
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
visible = false
[node name="PlayerSettings" type="Control" parent="." index="3"]
[node name="HeroDescription" parent="HeroSelect" index="2"]
editor/display_folded = true
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_right = 40.0
margin_bottom = 40.0
rect_pivot_offset = Vector2( 0, 0 )
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
margin_left = -2.0
margin_top = 125.0
margin_right = 366.0
margin_bottom = 288.0
text = "L Let s put oeuntahoeu nthaoeu ntaoheunt aoheunt hoaenth oaentuh Let s put oeuntahoeu nthaoeu ntaoheunt aoheunt hoaenth "
[node name="PlayerSettingsLabel" type="Label" parent="PlayerSettings" index="0"]
[node name="Title" type="Label" parent="HeroSelect" index="3"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 459.0
margin_top = -6.0
margin_right = 564.0
margin_bottom = 13.0
margin_left = 6.0
margin_top = 9.0
margin_right = 294.0
margin_bottom = 45.0
rect_pivot_offset = Vector2( 0, 0 )
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 4
text = "Player Settings"
custom_fonts/font = SubResource( 1 )
text = "Select your hero"
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="Username" type="TextEdit" parent="PlayerSettings" index="1"]
[node name="Username" type="TextEdit" parent="." index="1"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 457.0
margin_top = 27.0
margin_right = 636.0
margin_bottom = 47.0
margin_left = 43.0
margin_top = 391.0
margin_right = 384.0
margin_bottom = 424.0
rect_pivot_offset = Vector2( 0, 0 )
focus_mode = 2
mouse_filter = 0
@ -252,37 +120,40 @@ caret_blink = false
caret_blink_speed = 0.65
caret_moving_by_right_click = true
[node name="HeroSelectLabel" type="Label" parent="PlayerSettings" index="2"]
[node name="Spectating" type="CheckButton" parent="." index="2"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 460.0
margin_top = 66.0
margin_right = 538.0
margin_bottom = 80.0
margin_left = 36.0
margin_top = 439.0
margin_right = 237.0
margin_bottom = 479.0
rect_pivot_offset = Vector2( 0, 0 )
mouse_filter = 2
focus_mode = 2
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 4
text = "Hero Select:"
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
size_flags_vertical = 1
toggle_mode = true
enabled_focus_mode = 2
shortcut = null
group = null
text = "Spectating "
flat = false
align = 0
[node name="HeroSelect" type="OptionButton" parent="PlayerSettings" index="3"]
[node name="LevelSelect" type="OptionButton" parent="." index="3"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 457.0
margin_top = 94.0
margin_right = 645.0
margin_bottom = 114.0
rect_rotation = -0.0115615
margin_left = 45.0
margin_top = 529.0
margin_right = 411.0
margin_bottom = 570.0
rect_pivot_offset = Vector2( 0, 0 )
focus_mode = 2
mouse_filter = 0
@ -294,288 +165,127 @@ action_mode = 0
enabled_focus_mode = 2
shortcut = null
group = null
text = "Platform map"
flat = false
align = 0
selected = -1
items = [ ]
script = ExtResource( 2 )
selected = 0
items = [ "Platform map", null, false, 0, null, "City-like thing", null, false, 1, null, "Slide", null, false, 2, null ]
[node name="TeamLabel" type="Label" parent="PlayerSettings" index="4"]
[node name="Label" type="Label" parent="LevelSelect" index="1"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 459.0
margin_top = 132.0
margin_right = 558.0
margin_bottom = 146.0
margin_left = -5.0
margin_top = -37.0
margin_right = 39.0
margin_bottom = -15.0
rect_pivot_offset = Vector2( 0, 0 )
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 4
text = "Assigned team:"
text = "Map:"
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="Team" type="Label" parent="PlayerSettings" index="5"]
[node name="TeamLabel" type="Label" parent="." index="4"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 460.0
margin_top = 157.0
margin_right = 500.0
margin_bottom = 171.0
margin_left = 498.0
margin_top = 87.0
margin_right = 666.0
margin_bottom = 111.0
rect_pivot_offset = Vector2( 0, 0 )
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 4
text = "Your team:"
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="HeroDescriptionLabel" type="Label" parent="PlayerSettings" index="6"]
[node name="Team" type="Label" parent="." index="5"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 677.0
margin_top = -5.0
margin_right = 790.0
margin_bottom = 9.0
margin_left = 625.0
margin_top = 87.0
margin_right = 665.0
margin_bottom = 111.0
rect_pivot_offset = Vector2( 0, 0 )
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 4
text = "Hero Description:"
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="HeroDescription" type="RichTextLabel" parent="PlayerSettings" index="7"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 675.0
margin_top = 15.0
margin_right = 918.0
margin_bottom = 245.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = true
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
bbcode_enabled = false
bbcode_text = ""
visible_characters = -1
percent_visible = 1.0
meta_underlined = true
tab_size = 4
text = ""
scroll_active = true
scroll_following = false
selection_enabled = false
override_selected_font_color = false
[node name="HSeparator3" type="HSeparator" parent="." index="4"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 19.0
margin_top = 271.0
margin_right = 645.0
margin_bottom = 275.0
rect_pivot_offset = Vector2( 0, 0 )
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
[node name="CustomGame" type="Control" parent="." index="5"]
[node name="PlayerList" type="Label" parent="." index="6"]
editor/display_folded = true
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_right = 40.0
margin_bottom = 40.0
rect_pivot_offset = Vector2( 0, 0 )
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
[node name="Label" type="Label" parent="CustomGame" index="0"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 671.0
margin_top = 275.0
margin_right = 874.0
margin_bottom = 289.0
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = 480.0
margin_top = 130.0
margin_right = 981.0
margin_bottom = 408.0
rect_pivot_offset = Vector2( 0, 0 )
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 4
text = "Or, host or join a custom game:"
custom_fonts/font = SubResource( 3 )
text = "Waiting for players to connect...."
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="Server" type="Button" parent="CustomGame" index="1"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 665.0
margin_top = 300.0
margin_right = 752.0
margin_bottom = 335.0
rect_pivot_offset = Vector2( 0, 0 )
focus_mode = 2
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
toggle_mode = false
enabled_focus_mode = 2
shortcut = null
group = null
text = "Server"
flat = false
align = 1
[node name="LevelSelect" type="OptionButton" parent="CustomGame" index="2"]
[node name="Ready" type="CheckButton" parent="." index="7"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 770.0
margin_top = 300.0
margin_right = 895.0
margin_bottom = 335.0
margin_left = 493.0
margin_top = 436.0
margin_right = 628.0
margin_bottom = 476.0
rect_pivot_offset = Vector2( 0, 0 )
focus_mode = 2
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
toggle_mode = false
action_mode = 0
toggle_mode = true
enabled_focus_mode = 2
shortcut = null
group = null
text = "Platform map"
text = "Ready "
flat = false
align = 0
selected = 0
items = [ "Platform map", null, false, 0, null, "City-like thing", null, false, 1, null, "Slide", null, false, 2, null ]
[node name="Client" type="Button" parent="CustomGame" index="3"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 667.0
margin_top = 354.0
margin_right = 753.0
margin_bottom = 393.0
rect_pivot_offset = Vector2( 0, 0 )
focus_mode = 2
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
toggle_mode = false
enabled_focus_mode = 2
shortcut = null
group = null
text = "Client"
flat = false
align = 1
[node name="IPLabel" type="Label" parent="CustomGame" index="4"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 673.0
margin_top = 403.0
margin_right = 698.0
margin_bottom = 419.0
rect_pivot_offset = Vector2( 0, 0 )
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 4
text = "IP:"
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="IP" type="TextEdit" parent="CustomGame" index="5"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 707.0
margin_top = 401.0
margin_right = 851.0
margin_bottom = 419.0
rect_pivot_offset = Vector2( 0, 0 )
focus_mode = 2
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
text = "127.0.0.1"
readonly = false
highlight_current_line = false
syntax_highlighting = false
show_line_numbers = false
highlight_all_occurrences = false
override_selected_font_color = false
context_menu_enabled = true
smooth_scrolling = false
v_scroll_speed = 80.0
hiding_enabled = 0
wrap_lines = false
caret_block_mode = false
caret_blink = false
caret_blink_speed = 0.65
caret_moving_by_right_click = true
[node name="Singleplayer" type="Button" parent="CustomGame" index="6"]
[node name="StartGame" type="Button" parent="." index="8"]
visible = false
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 668.0
margin_top = 431.0
margin_right = 760.0
margin_bottom = 470.0
margin_left = 496.0
margin_top = 491.0
margin_right = 618.0
margin_bottom = 531.0
rect_pivot_offset = Vector2( 0, 0 )
focus_mode = 2
mouse_filter = 0
@ -586,93 +296,36 @@ toggle_mode = false
enabled_focus_mode = 2
shortcut = null
group = null
text = "Singleplayer"
text = "Start!"
flat = false
align = 1
[node name="VSeparator2" type="VSeparator" parent="." index="6"]
[node name="VSeparator" type="VSeparator" parent="." index="9"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 653.0
margin_top = 273.0
margin_right = 657.0
margin_bottom = 467.0
margin_left = 453.0
margin_top = 50.0
margin_right = 471.0
margin_bottom = 566.0
rect_pivot_offset = Vector2( 0, 0 )
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
[node name="JoinedGameLobby" type="Control" parent="." index="7"]
editor/display_folded = true
visible = false
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_right = 40.0
margin_bottom = 40.0
rect_pivot_offset = Vector2( 0, 0 )
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
[node name="PlayerListLabel" type="Label" parent="JoinedGameLobby" index="0"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 14.0
margin_top = 287.0
margin_right = 136.0
margin_bottom = 301.0
rect_pivot_offset = Vector2( 0, 0 )
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 4
text = "Connected Players:"
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="PlayerList" type="Label" parent="JoinedGameLobby" index="1"]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -7.0
margin_top = 287.0
margin_right = 639.0
margin_bottom = 433.0
rect_pivot_offset = Vector2( 0, 0 )
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 4
custom_fonts/font = SubResource( 4 )
text = "Waiting for players to connect...."
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="StartGame" type="Button" parent="JoinedGameLobby" index="2"]
[node name="Back" type="Button" parent="." index="10"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 13.0
margin_left = 834.0
margin_top = 443.0
margin_right = 135.0
margin_bottom = 483.0
margin_right = 978.0
margin_bottom = 471.0
rect_pivot_offset = Vector2( 0, 0 )
focus_mode = 2
mouse_filter = 0
@ -683,8 +336,9 @@ toggle_mode = false
enabled_focus_mode = 2
shortcut = null
group = null
text = "Start game"
text = "Exit to menu"
flat = false
align = 1
[editable path="HeroSelect"]

+ 166
- 0
scenes/menu.tscn View File

@ -0,0 +1,166 @@
[gd_scene load_steps=5 format=2]
[ext_resource path="res://assets/theme.tres" type="Theme" id=1]
[ext_resource path="res://scripts/menu.gd" type="Script" id=2]
[sub_resource type="DynamicFontData" id=1]
font_path = "res://assets/DejaVuSansMono.ttf"
[sub_resource type="DynamicFont" id=2]
size = 30
use_mipmaps = false
use_filter = false
font_data = SubResource( 1 )
[node name="Menu" type="Control"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_right = 1024.0
margin_bottom = 600.0
rect_pivot_offset = Vector2( 0, 0 )
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
theme = ExtResource( 1 )
script = ExtResource( 2 )
[node name="Title" type="Label" parent="." index="0"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 60.0
margin_top = 50.0
margin_right = 248.0
margin_bottom = 90.0
rect_pivot_offset = Vector2( 0, 0 )
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 4
custom_fonts/font = SubResource( 2 )
text = "VANAGLORIA"
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="Center" type="Control" parent="." index="1"]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -20.0
margin_top = -20.0
margin_right = 20.0
margin_bottom = 20.0
rect_pivot_offset = Vector2( 0, 0 )
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
[node name="Play" type="Button" parent="Center" index="0"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = -250.0
margin_top = -70.0
margin_right = 298.0
margin_bottom = -10.0
rect_pivot_offset = Vector2( 0, 0 )
focus_mode = 2
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
toggle_mode = false
enabled_focus_mode = 2
shortcut = null
group = null
text = "Quick Play"
flat = false
align = 1
[node name="CustomGame" type="Button" parent="Center" index="1"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = -252.0
margin_top = 14.0
margin_right = 300.0
margin_bottom = 75.0
rect_pivot_offset = Vector2( 0, 0 )
focus_mode = 2
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
toggle_mode = false
enabled_focus_mode = 2
shortcut = null
group = null
text = "Custom Game"
flat = false
align = 1
[node name="Singleplayer" type="Button" parent="Center" index="2"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = -251.0
margin_top = 104.0
margin_right = 299.0
margin_bottom = 170.0
rect_pivot_offset = Vector2( 0, 0 )
focus_mode = 2
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
toggle_mode = false
enabled_focus_mode = 2
shortcut = null
group = null
text = "Practice Range"
flat = false
align = 1
[node name="Quit" type="Button" parent="Center" index="3"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = -252.0
margin_top = 200.0
margin_right = 298.0
margin_bottom = 266.0
rect_pivot_offset = Vector2( 0, 0 )
focus_mode = 2
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
toggle_mode = false
enabled_focus_mode = 2
shortcut = null
group = null
text = "Quit"
flat = false
align = 1

+ 20
- 5
scenes/player.tscn View File

@ -1,8 +1,9 @@
[gd_scene load_steps=14 format=2]
[gd_scene load_steps=15 format=2]
[ext_resource path="res://scripts/player.gd" type="Script" id=1]
[ext_resource path="res://scripts/tp_camera.gd" type="Script" id=2]
[ext_resource path="res://scripts/player_name.gd" type="Script" id=3]
[ext_resource path="res://scenes/ability_icon.tscn" type="PackedScene" id=2]
[ext_resource path="res://scripts/tp_camera.gd" type="Script" id=3]
[ext_resource path="res://scripts/player_name.gd" type="Script" id=4]
[sub_resource type="CapsuleShape" id=1]
@ -365,10 +366,24 @@ percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="SwitchHero" parent="MasterOnly" index="2" instance=ExtResource( 2 )]
visible = false
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
margin_top = -107.0
margin_bottom = -98.0
cost = 100
ability_name = "Switch Hero"
display_progress = false
action = "switch_hero"
[node name="TPCamera" type="Spatial" parent="." index="5"]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.56913, 0 )
script = ExtResource( 2 )
script = ExtResource( 3 )
cam = NodePath("Camera")
pivot = NodePath("Pivot")
@ -434,6 +449,6 @@ align = 1
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
script = ExtResource( 3 )
script = ExtResource( 4 )

+ 82
- 0
scenes/singleplayer_lobby.tscn View File

@ -0,0 +1,82 @@
[gd_scene load_steps=4 format=2]
[ext_resource path="res://scenes/lobby.tscn" type="PackedScene" id=1]
[ext_resource path="res://assets/DejaVuSansMono.ttf" type="DynamicFontData" id=2]
[sub_resource type="DynamicFont" id=1]
size = 40
use_mipmaps = false
use_filter = false
font_data = ExtResource( 2 )
[node name="Lobby" instance=ExtResource( 1 )]
[node name="Label" type="Label" parent="." index="0"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 602.0
margin_top = 276.0
margin_right = 938.0
margin_bottom = 324.0
rect_pivot_offset = Vector2( 0, 0 )
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 4
custom_fonts/font = SubResource( 1 )
text = "Practice Range"
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="HeroSelect" parent="." index="1"]
margin_top = 62.0
margin_bottom = 62.0
[node name="Username" parent="." index="2"]
visible = false
margin_top = 308.0
margin_bottom = 341.0
[node name="Spectating" parent="." index="3"]
visible = false
[node name="LevelSelect" parent="." index="4"]
margin_top = 401.0
margin_bottom = 442.0
items = [ "Platform map", null, false, 0, null, "City-like thing", null, false, 1, null, "Slide", null, false, 2, null ]
[node name="TeamLabel" parent="." index="5"]
visible = false
[node name="Team" parent="." index="6"]
visible = false
[node name="PlayerList" parent="." index="7"]
visible = false
[node name="StartGame" parent="." index="8"]
margin_left = 41.0
margin_top = 486.0
margin_right = 163.0
margin_bottom = 526.0
text = "Play!"
[node name="VSeparator" parent="." index="9"]
visible = false
[editable path="HeroSelect"]

+ 23
- 0
scripts/ability_icon.gd View File

@ -6,13 +6,36 @@ onready var available = get_node("Available")
export var cost = 1
export var ability_name = "Ability"
export var display_progress = true
export var action = ""
# This is intended to be public
var disabled = false
func _ready():
get_node("Name").text = ability_name
var description
if action:
var primary = InputMap.get_action_list(action)[0]
if primary is InputEventMouseButton:
if primary.button_index == BUTTON_LEFT:
description = "Click"
elif primary.button_index == BUTTON_RIGHT:
description = "Right Click"
else:
description = "Scroll Click"
else:
description = primary.as_text()
else:
description = ""
get_node("Button").text = description
func is_pressed():
return Input.is_action_pressed(action)
func _process(delta):
if action and Input.is_action_pressed(action):
available.rect_position = Vector2(-25, -25) # Centered / not offset
else:
available.rect_position = Vector2(-30, -30)
if disabled:
available.hide()
bar.value = 0


+ 14
- 0
scripts/custom_game.gd View File

@ -0,0 +1,14 @@
extends Control
func _ready():
get_node("Server").connect("pressed", self, "_start_server")
get_node("Client").connect("pressed", self, "_start_client")
get_node("Back").connect("pressed", get_tree(), "change_scene", ["res://scenes/menu.tscn"])
func _start_server():
# Custom Game can assume we're playing as well
networking.start_server()
func _start_client():
var ip = get_node("IP").text
networking.start_client(ip)

+ 11
- 0
scripts/hero_select.gd View File

@ -22,3 +22,14 @@ func _ready():
for hero_index in range(hero_names.size()):
add_item(hero_names[hero_index], hero_index)
connect("item_selected", self, "set_hero")
func set_hero(hero):
select(hero)
networking.set_info_from_server("hero", hero)
func random_hero():
var hero = randi() % hero_names.size()
set_hero(hero)
return hero

+ 1
- 1
scripts/heroes/0.gd View File

@ -24,7 +24,7 @@ func control_player(state):
var boost_strength = 2
var boost_drain = 25 # Recall increased charge must be factored in
var cost = boost_drain * state.step
if Input.is_action_pressed("hero_0_boost") and switch_charge > cost:
if get_node("MasterOnly/Boost").is_pressed() and switch_charge > cost:
walk_speed *= 2
air_accel *= 3
switch_charge -= cost


+ 1
- 1
scripts/heroes/1_wall.gd View File

@ -5,7 +5,7 @@ var being_touched = 0
func _process(delta):
if being_touched > 0:
maker_node.switch_charge += touch_charge * delta
maker_node.build_charge(touch_charge * delta)
func init(maker):


+ 1
- 1
scripts/heroes/2.gd View File

@ -15,7 +15,7 @@ func _process(delta):
get_node("MasterOnly/Crosshair").set_text("\\/")
var overlapping = get_node("Area").get_overlapping_bodies().size()
switch_charge += delta * overlap_charge * overlapping
build_charge(delta * overlap_charge * overlapping)
sync func switch_gravity():
var area = get_node("Area")


+ 2
- 2
scripts/heroes/3.gd View File

@ -39,7 +39,7 @@ func _process(delta):
# Subtract and then add, so we can continously add this
switch_charge -= boost_charge
boost_charge = merged.switch_charge - original_charge
switch_charge += boost_charge
build_charge(boost_charge)
func control_player(state):
if !merged:
@ -90,7 +90,7 @@ func set_boosted(node, is_boosted):
sync func merge(node_name):
set_boosting(true)
var other = $"/root/Level/Players".get_node(node_name)
var other = util.get_player(node_name)
set_boosted(other, true)
merged = other


+ 3
- 3
scripts/heroes/4.gd View File

@ -46,8 +46,8 @@ func _process(delta):
var player = pick_from(players)
if player != -1:
# We get charge for just stunning, plus charge for how much linear velocity we cut out
switch_charge += stun_charge * delta
switch_charge += velocity_charge * players[player].get_linear_velocity().length() * delta
build_charge(stun_charge * delta)
build_charge(velocity_charge * players[player].linear_velocity.length() * delta)
rpc("stun", players[player].get_name(), get_node("TPCamera/Camera/Ray").get_collision_point())
is_stunning = true
@ -60,7 +60,7 @@ func _process(delta):
sync func stun(net_id, position):
# Stun the thing!
var player = get_node("/root/Level/Players/%s" % net_id)
var player = util.get_player(net_id)
player.set_linear_velocity(Vector3())
# Show the beam!


+ 2
- 2
scripts/heroes/5.gd View File

@ -63,10 +63,10 @@ func flick_input():
towards -= gravity
rpc("flick", flicking.get_name(), towards)
flicking = null
switch_charge += flick_charge
build_charge(flick_charge)
sync func flick(player_id, towards):
var who = $"/root/Level/Players".get_node(player_id)
var who = util.get_player(player_id)
if who.is_network_master():
var direction = towards - who.translation
var impulse = direction.normalized() * flick_strength * who.get_mass()


+ 4
- 1
scripts/heroes/5_portal.gd View File

@ -2,6 +2,7 @@ extends "res://scripts/placeable.gd"
var portal_charge = -5
var other
var index
var enemy_colors = [
Color("#d14013"),
@ -25,6 +26,8 @@ func _exit_tree():
func init(maker):
index = maker.placement.placed.size()
# If index is odd, we're the second (1, 3...), if even, first (0, 4...)
var second = index % 2 != 0
var is_friend = util.is_friendly(maker)
@ -62,5 +65,5 @@ func portal(player):
# With both axes, gravity could never bring us to hit the portal
var to = other.to_global(Vector3(spawn_distance,0,-spawn_distance))
player.set_translation(to)
maker_node.switch_charge += portal_charge
maker_node.build_charge(portal_charge)

+ 92
- 249
scripts/lobby.gd View File

@ -1,271 +1,114 @@
extends "res://scripts/args.gd"
extends Control
var SERVER_PORT = 54672
var MAX_PLAYERS = 50
var player_info = {}
var my_info = {}
var begun = false
var server_playing = true
var global_server_ip = "nv.cosinegaming.com"
var players_done = []
var is_connected = false # Technically this can be done with ENetcetera but it's easier this way
func setup_options():
var opts = Options.new()
opts.set_banner(('A non-violent MOBA inspired by Overwatch and Zineth'))
opts.add('-singleplayer', false, 'Whether to run singeplayer, starting immediately')
opts.add('-server', false, 'Whether to run as server')
opts.add('-client', false, 'Immediately connect as client')
opts.add('-silent', false, 'If the server is not playing, merely serving')
opts.add('-hero', 'r', 'Your choice of hero (index)')
opts.add('-level', 'r', 'Your choice of level (index) - server only!')
opts.add('-start-game', false, 'Join as a client and immediately start the game')
opts.add('-ai', true, 'Run this client as AI')
opts.add('-no-record', true, "Don't record this play for AI later")
opts.add('-h', false, "Print help")
return opts
func option_sel(button_name, option):
var button = get_node(button_name)
if option == "r":
option = randi() % button.get_item_count()
else:
option = int(option)
button.select(option)
onready var hero_select = get_node("HeroSelect/Hero")
onready var level_select = get_node("LevelSelect")
onready var start_game_button = get_node("StartGame")
onready var ready_button = get_node("Ready")
func _ready():
my_info.version = [0,0,0] # Semantic versioning: [0].[1].[2]
randomize()
get_node("GameBrowser/Play").connect("pressed", self, "connect_global_server")
get_node("PlayerSettings/HeroSelect").connect("item_selected", self, "select_hero")
get_node("PlayerSettings/Username").connect("text_changed", self, "resend_name")
get_node("JoinedGameLobby/StartGame").connect("pressed", self, "start_game")
get_node("CustomGame/Server").connect("pressed", self, "_server_init")
get_node("CustomGame/Client").connect("pressed", self, "_client_init")
get_node("CustomGame/Singleplayer").connect("pressed", self, "_singleplayer_init")
get_node("CustomGame/LevelSelect").connect("item_selected", self, "select_level")
var o = setup_options()
o.parse()
if o.get_value("-silent"):
server_playing = false # TODO: Uncaps :(
if o.get_value("-hero"):
var hero = o.get_value("-hero")
option_sel("PlayerSettings/HeroSelect", hero)
# For some reason, calling option_sel doesn't trigger the actual selection
select_hero(get_node("PlayerSettings/HeroSelect").get_selected_id())
if o.get_value("-level"):
option_sel("CustomGame/LevelSelect", o.get_value("-level"))
if o.get_value("-server"):
call_deferred("_server_init")
if o.get_value("-client"):
call_deferred("_client_init")
if o.get_value("-start-game"):
my_info.start_game = true
call_deferred("_client_init")
if o.get_value("-singleplayer"):
call_deferred("_singleplayer_init")
if o.get_value("-ai"):
my_info.is_ai = true
if not o.get_value("-no-record") and not o.get_value("-ai"):
my_info.record = true
if o.get_value('-h'):
o.print_help()
quit()
get_tree().connect("network_peer_connected", self, "_player_connected")
get_tree().connect("network_peer_disconnected", self, "_player_disconnected")
get_tree().connect("connected_to_server", self, "_connected_ok")
func connect_global_server():
_client_init(global_server_ip)
func _client_init(ip=null):
collect_info()
var peer = NetworkedMultiplayerENet.new()
if not ip:
ip = get_node("CustomGame/IP").get_text()
ip = IP.resolve_hostname(ip)
peer.create_client(ip, SERVER_PORT)
get_tree().set_network_peer(peer)
get_node("CustomGame/Client").set_text("Clienting!")
func _singleplayer_init():
collect_info()
var peer = NetworkedMultiplayerENet.new()
peer.create_server(SERVER_PORT, 1)
get_tree().set_network_peer(peer)
player_info[1] = my_info
start_game()
func _server_init():
collect_info()
var peer = NetworkedMultiplayerENet.new()
peer.create_server(SERVER_PORT, MAX_PLAYERS)
get_tree().set_network_peer(peer)
is_connected = true
get_node("CustomGame/Server").set_text("Serving!")
get_node("JoinedGameLobby").show()
if server_playing:
player_info[1] = my_info
# Connect (to networking)
get_node("Username").connect("text_changed", self, "_send_name")
get_node("Spectating").connect("toggled", self, "_set_info_callback", ["spectating"])
ready_button.connect("toggled", self, "_set_info_callback", ["ready"])
start_game_button.connect("pressed", networking, "start_game")
# Connect (from networking)
networking.connect("info_updated", self, "render_player_list")
get_tree().connect("connected_to_server", self, "_connected")
# Connect (static)
get_node("Back").connect("pressed", self, "_exit_to_menu")
var spectating = util.args.get_value("-silent")
get_node("Spectating").pressed = spectating
# Shown, maybe, in _check_begun
start_game_button.hide()
if get_tree().is_network_server():
start_game_button.show()
func _player_connected(id):
pass
if get_tree().is_network_server():
# We put level in our players dict because it's automatically broadcast to other players
var level = util.args.get_value("-level")
if level == "r":
level = randi() % level_select.get_item_count()
level = int(level)
_set_level(level)
level_select.show()
level_select.select(level)
level_select.connect("item_selected", self, "_set_level")
else:
level_select.hide()
func _player_disconnected(id):
if get_tree().is_network_server():
rpc("unregister_player", id)
call_deferred("render_player_list")
_connected()
func _connected_ok():
rpc("register_player", get_tree().get_network_unique_id(), my_info)
if "start_game" in my_info and my_info.start_game:
rpc_id(1, "start_game")
get_node("JoinedGameLobby").show()
is_connected = true
func _connected():
func collect_info():
if not "username" in my_info:
my_info.username = get_node("PlayerSettings/Username").get_text()
if not "hero" in my_info:
my_info.hero = get_node("PlayerSettings/HeroSelect").get_selected_id()
if not "is_right_team" in my_info:
my_info.is_right_team = false # Server assigns team, wait for that
_send_name()
networking.set_info_from_server("ready", false)
networking.set_info_from_server("spectating", util.args.get_value("-silent"))
networking.set_info_from_server("begun", false)
if util.args.get_value("-hero") == "r":
hero_select.random_hero()
else:
hero_select.set_hero(int(util.args.get_value("-hero")))
remote func register_player(new_peer, info):
player_info[new_peer] = info
render_player_list()
if (get_tree().is_network_server()):
var right_team_count = 0
# Send current players' info to new player
for old_peer in player_info:
# Send new player, old player's info
rpc_id(new_peer, "register_player", old_peer, player_info[old_peer])
if old_peer != new_peer:
# We need to assign team later, so count current
if player_info[old_peer].is_right_team:
right_team_count += 1
# You'd think this part could be met with a simple `rpc(`, but actually it can't
# My best guess is this is because we haven't registered the names yet, but I'm not sure (TODO)
if old_peer != 1:
# Send old player, new player's info (not us, no infinite loop)
rpc_id(old_peer, "register_player", new_peer, info)
if begun:
rpc_id(old_peer, "spawn_player", new_peer)
rpc_id(old_peer, "begin_player_deferred", new_peer) # Spawning is deferred
if not server_playing:
# We didn't catch this in player_info
rpc_id(1, "spawn_player", new_peer)
rpc_id(1, "begin_player_deferred", new_peer) # Spawning is deferred
var assign_right_team = right_team_count * 2 < player_info.size()
rpc("assign_team", new_peer, assign_right_team)
if not begun and player_info.size() == MAX_PLAYERS:
start_game()
if begun:
rpc_id(new_peer, "pre_configure_game", my_info.level)
rpc_id(new_peer, "post_configure_game")
if util.args.get_value("-start-game"):
networking.start_game()
sync func unregister_player(peer):
player_info.erase(peer)
get_node("/root/Level/Players/%d" % peer).queue_free()
func _set_level(level):
networking.level = level
func select_hero(hero):
var description = get_node("PlayerSettings/HeroSelect").hero_text[hero]
get_node("PlayerSettings/HeroDescription").set_text(description)
if is_connected:
rpc("set_hero", get_tree().get_network_unique_id(), hero)
# Because of the annoying way callbacks work (automatic parameter, optional parameter)
# We need a utility function for making these kinds of callbacks for set_info
func _set_info_callback(value, key):
networking.set_info_from_server(key, value)
sync func set_hero(peer, hero):
player_info[peer].hero = hero
networking.players[peer].hero = hero
render_player_list()
func resend_name():
var name = get_node("PlayerSettings/Username").get_text()
rpc("set_name", get_tree().get_network_unique_id(), name)
sync func set_name(peer, name):
player_info[peer].username = name
render_player_list()
func _send_name():
var name = get_node("Username").text
networking.set_info_from_server("username", name)
sync func assign_team(peer, is_right_team):
player_info[peer].is_right_team = is_right_team
if peer == get_tree().get_network_unique_id():
if is_right_team:
get_node("PlayerSettings/Team").set_text("Right Team")
else:
get_node("PlayerSettings/Team").set_text("Left Team")
render_player_list()
func _check_begun():
if networking.players.has(1) and networking.players[1].has("begun"):
var game_started = networking.players[1].begun
if game_started:
start_game_button.show()
start_game_button.text = "Join game"
# The "Ready" toggle doesn't really make sense on a started game
ready_button.hide()
func render_player_list():
if has_node("PlayerSettings"):
var list = ""
var hero_names = get_node("PlayerSettings/HeroSelect").hero_names
for p in player_info:
list += "%-15s" % player_info[p].username
list += "%-20s" % hero_names[player_info[p].hero]
if player_info[p].is_right_team:
list += "Right Team"
else:
list += "Left Team"
_check_begun()
var list = ""
var hero_names = hero_select.hero_names
for p in networking.players:
var player = networking.players[p]
# A spectating server is just a dedicated server, ignore it
if p and player.has("spectating") and not (player.spectating and p == 1):
var username = player.username if player.has("username") else "Loading..."
list += "%-15s " % username
var hero = hero_names[player.hero] if player.has("hero") else "Loading..."
list += "%-10s " % hero
var team = "Loading..."
if player.has("is_right_team"):
if player.is_right_team:
team = "Right Team"
else:
team = "Left Team"
list += "%-11s" % team
var ready_text = "Ready" if player.has("ready") and player.ready else ""
list += "%-6s" % ready_text
if player.has("spectating") and player.spectating:
list += "Spectating"
list += "\n"
get_node("JoinedGameLobby/PlayerList").set_text(list)
sync func start_game():
my_info.level = get_node("CustomGame/LevelSelect").get_selected_id()
rpc("pre_configure_game", my_info.level)
sync func done_preconfiguring(who):
players_done.append(who)
if players_done.size() == player_info.size():
# We call deferred in case singleplayer has placing the player in queue still
call_deferred("rpc", "post_configure_game")
sync func spawn_player(p):
var hero = player_info[p].hero
var player = load("res://scenes/heroes/" + str(hero) + ".tscn").instance()
player.set_name(str(p))
player.set_network_master(p)
player.player_info = player_info[p]
get_node("/root/Level/Players").call_deferred("add_child", player)
sync func pre_configure_game(level):
begun = true
my_info.level = level # Remember the level for future player registration
var self_peer_id = get_tree().get_network_unique_id()
# Remove the interface so as to not fuck with things
# But we still need the lobby alive to deal with networking!
for element in get_node("/root/Lobby").get_children():
element.queue_free()
var world = load("res://scenes/levels/%d.tscn" % level).instance()
get_node("/root").add_child(world)
# Load all players (including self)
for p in player_info:
player_info[p].level = level
spawn_player(p)
rpc_id(1, "done_preconfiguring", self_peer_id)
func begin_player(peer):
get_node("/root/Level/Players/%d" % peer).begin()
remote func begin_player_deferred(peer):
call_deferred("begin_player", peer)
sync func reset_state():
players_done = []
get_node("/root/Level").queue_free()
get_node("PlayerList").set_text(list)
sync func post_configure_game():
# Begin all players (including self)
for p in player_info:
begin_player_deferred(p)
func _exit_to_menu():
get_tree().network_peer.close_connection()
get_tree().change_scene("res://scenes/menu.tscn")

+ 105
- 0
scripts/matchmaking.gd View File

@ -0,0 +1,105 @@
extends Node
var SERVER_TO_SERVER_PORT = 54671
var MATCHMAKING_PORT = 54672
var GAME_SIZE = 6
# Number of games we can make without blowing up the computer
var MAX_GAMES = 50 # Totally random guess
var next_port = 54673
# Filled with queue info which contains
# { "netid" }
var skirmishing_players = []
var skirmish
# To avoid the confusion of the gameSERVERS being CLIENTS,
# we just call them games whenever possible
# var games = []
var game_connections = []
var game_streams = []
# Matchmaker to game servers
var matchmaker_to_games
enum messages {
ready_to_connect,
}
onready var lobby = get_node("..")
func _ready():
# By default, having this node doesn't do naything
# You must call start_matchmaker to enable it
# If not called, don't call _process (= don't matchmake)
set_process(false)
func start_matchmaker():
# Actually run the matchmaker
set_process(true)
# Setup skirmish server
skirmish = spawn_server()
# Set up communication between GAMESERVERS
# This is necessary for eg, when a player leaves to backfill
matchmaker_to_games = TCP_Server.new()
if matchmaker_to_games.listen(SERVER_TO_SERVER_PORT):
print("Error, could not listen")
# Use ENet for matchmaker because we can (makes client code cleaner)
var matchmaker_to_players = NetworkedMultiplayerENet.new()
print("Starting matchmaker on port " + str(MATCHMAKING_PORT))
matchmaker_to_players.create_server(MATCHMAKING_PORT, MAX_GAMES)
get_tree().set_network_peer(matchmaker_to_players)
matchmaker_to_players.connect("peer_connected", self, "queue")
func _process(delta):
# Manage connection to GAMESERVERS (not clients)
if matchmaker_to_games.is_connection_available(): # check if a gameserver's trying to connect
var game = matchmaker_to_games.take_connection() # accept connection
game_connections.append(game) # store the connection
var stream = PacketPeerStream.new()
stream.set_stream_peer(game) # bind peerstream to new client
game_streams.append(stream) # make new data transfer object for game
for stream in game_streams:
if stream.get_available_packet_count():
var message = stream.get_var()
if message == messages.ready_to_connect:
var port = stream.get_var()
print("Server " + str(port) + " has requested connection")
skirmish_to_game(port, GAME_SIZE)
func queue(netid):
print("Player " + str(netid) + " connected.")
add_to_game(netid, skirmish)
skirmishing_players.append(netid)
check_queue()
# # This is only for clients, but it's in here so we can rpc it easily
# slave func join_game(port):
# #
func add_to_game(netid, port):
networking.rpc_id(netid, "reconnect", port)
func skirmish_to_game(port, count=1):
for i in range(count):
if not skirmishing_players.size():
return false
print(skirmishing_players[0])
print("to")
print(port)
add_to_game(skirmishing_players[0], port)
return true
func check_queue():
# Prefer making a full game to backfilling
if skirmishing_players.size() >= GAME_SIZE:
spawn_server()
# games.append(port)
func spawn_server():
OS.execute("util/server.sh", ['-port='+str(next_port)], false)
next_port += 1
return (next_port - 1) # Return original port

+ 56
- 0
scripts/menu.gd View File

@ -0,0 +1,56 @@
extends Control
func _ready():
randomize()
_gui_setup()
_arg_actions()
# GUI
func _gui_setup():
get_node("Center/Play").connect("pressed", self, "_find_game")
get_node("Center/CustomGame").connect("pressed", self, "_custom_game")
get_node("Center/Singleplayer").connect("pressed", self, "_singleplayer")
get_node("Center/Quit").connect("pressed", get_tree(), "quit")
func _find_game():
# var ip = networking.global_server_ip
var ip = util.args.get_value("-ip")
var port = networking.matchmaking.MATCHMAKING_PORT
networking.start_client(ip, port)
func _custom_game():
get_tree().change_scene("res://scenes/custom_game.tscn")
func _singleplayer():
networking.start_server()
get_tree().change_scene("res://scenes/singleplayer_lobby.tscn")
# Command line
func _option_sel(button_name, option):
var button = get_node(button_name)
if option == "r":
option = randi() % button.get_item_count()
else:
option = int(option)
button.select(option)
func _arg_actions():
var o = util.args
# if o.get_value("-ai"):
# my_info.is_ai = true
# if not o.get_value("-no-record") and not o.get_value("-ai"):
# my_info.record = true
if o.get_value("-server"):
networking.start_server()
if o.get_value("-matchmaker"):
networking.matchmaking.start_matchmaker()
if o.get_value("-client"):
networking.start_client()
if o.get_value("-singleplayer"):
_singleplayer()
if o.get_value('-h'):
o.print_help()
quit()

+ 193
- 0
scripts/networking.gd View File

@ -0,0 +1,193 @@
extends Node
onready var matchmaking = preload("res://scripts/matchmaking.gd").new()
remote var players = {}
var players_done = []
var global_server_ip = "nv.cosinegaming.com"
var matchmaker_tcp
var right_team_next = false
var level
signal info_updated
func _ready():
add_child(matchmaking)
get_tree().connect("network_peer_disconnected", self, "unregister_player")
get_tree().connect("network_peer_connected", self, "register_player")
get_tree().connect("connected_to_server", self, "_on_connect")
connect("info_updated", self, "_check_info")
func start_client(ip="", port=0):
if not ip:
ip = util.args.get_value("-ip")
ip = IP.resolve_hostname(ip)
if not port:
port = util.args.get_value("-port")
var peer = NetworkedMultiplayerENet.new()
print("Connecting to " + ip + ":" + str(port))
peer.create_client(ip, port)
get_tree().set_network_peer(peer)
get_tree().change_scene("res://scenes/lobby.tscn")
remote func reconnect(port):
# Reset previously known players
players = {}
start_client("", port)
func _connect_to_matchmaker(game_port):
var matchmaker_peer = StreamPeerTCP.new()
matchmaker_peer.connect_to_host("127.0.0.1", matchmaking.SERVER_TO_SERVER_PORT)
var matchmaker_tcp = PacketPeerStream.new()
matchmaker_tcp.set_stream_peer(matchmaker_peer)
matchmaker_tcp.put_var(matchmaking.messages.ready_to_connect)
matchmaker_tcp.put_var(game_port)
func start_server(port=0):
if not port:
port = util.args.get_value("-port")
var peer = NetworkedMultiplayerENet.new()
print("Starting server on port " + str(port))
peer.create_server(port, matchmaking.GAME_SIZE)
get_tree().set_network_peer(peer)
# As soon as we're listening, let the matchmaker know
_connect_to_matchmaker(port)
register_player(get_tree().get_network_unique_id())
if util.args.get_value("-silent"):
set_info("spectating", true)
get_tree().change_scene("res://scenes/lobby.tscn")
master func _start_game():
rpc("_pre_configure_game", level)
func start_game():
rpc_id(1, "_start_game")
func send_all_info(new_peer):
for p in players:
if p != new_peer:
for key in players[p]:
var val = players[p][key]
# TODO: This broadcasts every connected peer,
# which isn't really a problem but it's lazy
set_info(key, val, p)
func _right_team_next():
var right_team_count = 0
for p in players:
var player = players[p]
if player.has("is_right_team") and player.is_right_team:
right_team_count += 1
return (right_team_count <= players.size() / 2)
remote func register_player(new_peer):
if get_tree().is_network_server():
# I tell new player about all the existing people
send_all_info(new_peer)
set_info("is_right_team", _right_team_next(), new_peer)
sync func unregister_player(peer):
players.erase(peer)
var p = util.get_player(peer)
if p:
p.queue_free()
emit_signal("info_updated")
sync func _set_info(key, value, peer):
if not players.has(peer):
players[peer] = {}
players[peer][key] = value
emit_signal("info_updated")
master func set_info(key, value, peer=0):
if not peer:
peer = get_tree().get_network_unique_id()
rpc("_set_info", str(key), value, peer)
# When connectivity is not yet guaranteed, the only one we know is always
# connected to everyone is the server. So in initial handshakes, it's better to
# tell the server what to tell everyone to do
func set_info_from_server(key, value, peer=0):
if not peer:
peer = get_tree().get_network_unique_id()
rpc_id(1, "set_info", key, value, peer)
func _on_connect():
register_player(get_tree().get_network_unique_id())
emit_signal("info_updated")
func _check_info():
# Check for "everyone is ready"
# Only have 1 person check this, might as well be server
if get_tree().is_network_server():
var ready = true
for p in players:
if not players[p].has("spectating") or not players[p].spectating:
if not players[p].has("ready") or not players[p].ready:
ready = false
if ready:
start_game()
sync func _spawn_player(p):
var hero = 0
if players[p].has("hero"):
hero = players[p].hero
var player = load("res://scenes/heroes/" + str(hero) + ".tscn").instance()
player.set_name(str(p))
player.set_network_master(p)
player.player_info = players[p]
get_node("/root/Level/Players").add_child(player)
func _begin_player(p):
var player = util.get_player(p)
player.begin()
sync func _pre_configure_game(level):
var self_peer_id = get_tree().get_network_unique_id()
var self_begun = players[self_peer_id].begun
if not self_begun:
get_node("/root/Lobby").hide()
var world = load("res://scenes/levels/%d.tscn" % level).instance()
get_node("/root").add_child(world)
# Load all players (including self)
for p in players:
if not players[p].spectating:
var existing_player = util.get_player(p)
if not self_begun or not existing_player:
_spawn_player(p)
for p in players:
if not players[p].spectating:
# Begin requires all players
_begin_player(p)
# Why do we check first? Weird error. It's because set_info triggers a
# start_game if everyone is ready
# This causes a stack overflow if we call it from here repeatedly
# So we only change it once, only start_game twice, and avoida segfault
if not self_begun:
print("setting my begun to true")
set_info("begun", true)
rpc_id(1, "_done_preconfiguring", self_peer_id)
sync func _done_preconfiguring(who):
players_done.append(who)
if players_done.size() == players.size():
print("done")
rpc("_post_configure_game")
sync func _post_configure_game():
# Begin all players (including self)
# TODO: What do? Maybe, unpause game?
pass
sync func reset_state():
players_done = []
get_node("/root/Level").queue_free()

+ 27
- 6
scripts/player.gd View File

@ -20,6 +20,7 @@ var movement_charge = 0.15 # In percent per meter (except when heroes change tha
onready var switch_text = get_node("MasterOnly/ChargeBar/ChargeText")
onready var switch_bar = get_node("MasterOnly/ChargeBar")
onready var switch_bar_extra = get_node("MasterOnly/ChargeBar/Extra")
onready var switch_hero_action = get_node("MasterOnly/SwitchHero")
var fall_height = -400 # This is essentially the respawn timer
var switch_height = -150 # At this point, stop adding to switch_charge. This makes falls not charge you too much
@ -51,13 +52,15 @@ func _ready():
set_process_input(true)
debug_node = get_node("/root/Level/Debug")
_set_color()
if is_network_master():
get_node("TPCamera/Camera/Ray").add_exception(self)
get_node(tp_camera).set_enabled(true)
spawn()
get_node(tp_camera).cam_view_sensitivity = 0.05
if "is_ai" in player_info and player_info.is_ai and not ai_instanced:
add_child(preload("res://scenes/ai.tscn").instance())
ai_instanced = true
spawn()
else:
get_node("PlayerName").set_text(player_info.username)
# Remove HUD
@ -81,11 +84,12 @@ func _process(delta):
var vel = get_linear_velocity()
if translation.y < switch_height:
vel.y = 0 # Don't gain charge from falling when below switch_height
switch_charge += movement_charge * vel.length() * delta
build_charge(movement_charge * vel.length() * delta)
switch_text.set_text("%d%%" % int(switch_charge)) # We truncate, rather than round, so that switch is displayed AT 100%
if switch_charge >= 100:
# Let switch_charge keep building, because we use it for walk_speed and things
switch_text.set_text("100%% (%.f)\nQ - Switch hero" % switch_charge)
switch_hero_action.show()
else:
switch_hero_action.hide()
if switch_charge > switch_charge_cap:
# There is however a cap
switch_charge = switch_charge_cap
@ -115,6 +119,21 @@ func _exit_tree():
# Functions
# =========
# Build all charge with a multiplier for ~~balance~~
func build_charge(amount):
# If we used build_charge to cost charge, don't mess with it!
if amount > 0:
var losing_advantage = 1.2
var uncapped_advantage = 1.3
var obj = get_node("/root/Level/FullObjective/Objective")
if (obj.left > obj.right) == player_info.is_right_team:
# Is losing (left winning, we're on right or vice versa)
amount *= losing_advantage
if obj.right_active != player_info.is_right_team and obj.active:
# Point against us (right active and left, or vice versa)
amount *= uncapped_advantage
switch_charge += amount
sync func spawn():
emit_signal("spawn")
if "record" in player_info:
@ -158,10 +177,13 @@ func event_to_obj(event):
return d
func begin():
_set_color()
func _set_color():
master_player = util.get_master_player()
# Set color to blue (teammate) or red (enemy)
var color
if master_player and master_player.player_info.is_right_team == player_info.is_right_team:
if master_player.player_info.is_right_team == player_info.is_right_team:
color = friend_color
else:
color = enemy_color
@ -286,7 +308,6 @@ sync func switch_hero(hero):
get_node("/root/Level/Players").call_deferred("add_child", new_hero)
# We must wait until after _ready is called, so that we don't end up at spawn
new_hero.call_deferred("set_status", get_status())
new_hero.call_deferred("begin")
queue_free()
func write_recording():


+ 33
- 1
scripts/util.gd View File

@ -1,7 +1,20 @@
extends Node
var version = [0,0,0] # Semantic versioning: [0].[1].[2]
var args
onready var Options = preload("res://scripts/args.gd").new().Options
func _ready():
args = _get_args()
func get_master_player():
var path = "/root/Level/Players/%d" % get_tree().get_network_unique_id()
return get_player(get_tree().get_network_unique_id())
func get_player(netid):
# We not %d? Because sometimes we need to do get_player(thing.get_name())
var path = "/root/Level/Players/%s" % str(netid)
if has_node(path):
return get_node(path)
else:
@ -14,3 +27,22 @@ func is_friendly(player):
else:
return true # Doesn't matter, we're headless
func _get_args():
var opts = Options.new()
opts.set_banner(('A non-violent MOBA inspired by Overwatch and Zineth'))
opts.add('-singleplayer', false, 'Whether to run singeplayer, starting immediately')
opts.add('-server', false, 'Whether to run as server')
opts.add('-matchmaker', false, 'Whether to be the sole matchmaker')
opts.add('-client', false, 'Immediately connect as client')
opts.add('-silent', false, 'If the server is not playing, merely serving')
opts.add('-ip', '127.0.0.1', 'The ip to connect to (client only!)')
opts.add('-port', 54673, 'The port to run a server on or connect to')
opts.add('-hero', 'r', 'Your choice of hero (index)')
opts.add('-level', 'r', 'Your choice of level (index) - server only!')
opts.add('-start-game', false, 'Join as a client and immediately start the game')
opts.add('-ai', true, 'Run this client as AI')
opts.add('-no-record', true, "Don't record this play for AI later")
opts.add('-h', false, "Print help")
opts.parse()
return opts

+ 1
- 0
util/matchmaker.sh View File

@ -0,0 +1 @@
godot-server -matchmaker

+ 1
- 1
util/server.sh View File

@ -1,2 +1,2 @@
godot -level=2 -silent -server -start-game
godot-server -level=2 -silent -server "$@"

+ 1
- 1
util/start-multiple.sh View File

@ -10,5 +10,5 @@ fi
util/open-multiple.sh $count "$@"
sleep 1
godot -start-game "$@" &
godot -client -start-game "$@" &

Loading…
Cancel
Save