Compare commits

...

17 Commits

Author SHA1 Message Date
21077b6e53
Allow manual build of stage 2020-07-26 00:07:25 -04:00
6a3a225931
Update subdomain 2020-05-21 19:28:37 -04:00
711f1e69f6
Update prod 2020-05-21 19:27:01 -04:00
95f1df755e
Update submodule 2020-05-20 15:30:45 -04:00
db334fc573
Update submodules 2020-05-20 15:06:58 -04:00
7f5e6a1fec
Switch accounts 2020-05-20 14:40:55 -04:00
4f47f758e7 Spellcheck 2020-05-05 03:28:13 -04:00
bf546793e2
Update READMEs 2020-05-05 03:20:58 -04:00
c980235726
Tweaks, changes, and bugfixes 2020-05-04 23:09:39 -04:00
cc443704f8
UDP movement 2020-05-04 06:42:47 -04:00
45673816bf
Non-grid UDP movement 2020-05-04 06:38:58 -04:00
3ee6551013
Refactor and cleanup of server code 2020-05-04 04:10:19 -04:00
6726982ee6
Client changes and tweaks 2020-05-04 02:45:04 -04:00
0e2eb924c7
Rename workflows 2020-05-04 01:19:42 -04:00
9126cbad77
Enable export suffixes 2020-05-04 01:07:14 -04:00
279b249f2b
Removed 'embed_pck=true' 2020-05-04 00:51:06 -04:00
bb8bea4562
Add windows export 2020-05-04 00:47:36 -04:00
25 changed files with 674 additions and 249 deletions

@ -4,13 +4,14 @@ on:
push:
branches:
- master
workflow_dispatch: {}
jobs:
godot:
runs-on: ubuntu-latest
strategy:
matrix:
platform: [linux]
platform: [linux, win32, win64]
steps:
- name: Checkout
uses: actions/checkout@v2
@ -20,15 +21,21 @@ jobs:
run: |
mkdir -p client/templates
wget http://godot-enet.cloudsumu.com.s3-website-us-east-1.amazonaws.com/export-templates/debug/$PLATFORM -O client/templates/$PLATFORM
- name: Configure
env:
PLATFORM: ${{ matrix.platform }}
run: |
if [ "$PLATFORM" = "win32" ] || [ "$PLATFORM" = "win64" ] ; then
echo "::set-env name=EXPORT_SUFFIX::.exe"
fi
- name: Build
id: build
uses: josephbmanley/build-godot-action@v1.4.0
with:
name: defend-together
name: defend-together${{ env.EXPORT_SUFFIX }}
preset: ${{ matrix.platform }}
projectDir: client
debugMode: 'true'
package: 'true'
- name: Upload Artifact
uses: actions/upload-artifact@v2
with:
@ -64,9 +71,9 @@ jobs:
args: --follow-symlinks --delete
env:
SOURCE_DIR: infrastructure/cloudformation
AWS_REGION: "us-east-1"
DEST_DIR: dt/stage/cloudformation
AWS_S3_BUCKET: sumu-stacks
AWS_REGION: "us-east-2"
DEST_DIR: stage/cloudformation
AWS_S3_BUCKET: dt-deployment-bucket
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Configure AWS Credentials
@ -74,11 +81,11 @@ jobs:
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
aws-region: us-east-2
- name: Deploy to AWS CloudFormation
uses: aws-actions/aws-cloudformation-github-deploy@v1
with:
name: dt-infrastructure-stage
template: infrastructure/cloudformation/dt/top.yaml
capabilities: "CAPABILITY_NAMED_IAM,CAPABILITY_IAM"
parameter-overrides: VpcId=${{ secrets.VPC_ID }},SubDomain=stage.dt,Domain=${{ secrets.DOMAIN }},environment=stage,DockerTag=stage,release=stage,PublicSubnets=${{ secrets.SUBNET_IDS }}
parameter-overrides: VpcId=${{ secrets.VPC_ID }},SubDomain=stage,Domain=${{ secrets.DOMAIN }},environment=stage,DockerTag=stage,release=stage,PublicSubnets=${{ secrets.SUBNET_IDS }}

@ -1,4 +1,4 @@
name: Push Dev Infrastructure
name: Push Dev
on:
push:
@ -19,8 +19,8 @@ jobs:
args: --follow-symlinks --delete
env:
SOURCE_DIR: infrastructure/cloudformation
AWS_REGION: "us-east-1"
DEST_DIR: dt/develop/cloudformation
AWS_S3_BUCKET: sumu-stacks
AWS_REGION: "us-east-2"
DEST_DIR: develop/cloudformation
AWS_S3_BUCKET: dt-deployment-bucket
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

@ -1,4 +1,4 @@
name: Release Server
name: Publish Release
on:
release:
@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
platform: [linux]
platform: [linux, win32, win64]
steps:
- name: Checkout
uses: actions/checkout@v2
@ -19,16 +19,21 @@ jobs:
PLATFORM: ${{ matrix.platform }}
run: |
mkdir -p client/templates
wget http://godot-enet.cloudsumu.com.s3-website-us-east-1.amazonaws.com/export-templates/debug/$PLATFORM -O client/templates/$PLATFORM
wget http://godot-enet.cloudsumu.com.s3-website-us-east-1.amazonaws.com/export-templates/release/$PLATFORM -O client/templates/$PLATFORM
- name: Configure
env:
PLATFORM: ${{ matrix.platform }}
run: |
if [ "$PLATFORM" = "win32" ] || [ "$PLATFORM" = "win64" ] ; then
echo "::set-env name=EXPORT_SUFFIX::.exe"
fi
- name: Build
id: build
uses: josephbmanley/build-godot-action@v1.4.0
with:
name: defend-together
name: defend-together${{ env.EXPORT_SUFFIX }}
preset: ${{ matrix.platform }}
projectDir: client
debugMode: 'true'
package: 'true'
- id: get_tag
name: Get Tag
env:
@ -122,9 +127,9 @@ jobs:
args: --follow-symlinks --delete
env:
SOURCE_DIR: infrastructure/cloudformation
AWS_REGION: "us-east-1"
DEST_DIR: dt/production/cloudformation
AWS_S3_BUCKET: sumu-stacks
AWS_REGION: "us-east-2"
DEST_DIR: production/cloudformation
AWS_S3_BUCKET: dt-deployment-bucket
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Configure AWS Credentials
@ -132,7 +137,7 @@ jobs:
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
aws-region: us-east-2
- name: Deploy to AWS CloudFormation
uses: aws-actions/aws-cloudformation-github-deploy@v1
with:

@ -1,9 +1,29 @@
# ludum-dare-46
![Build Stage](https://github.com/josephbmanley/defend-together/workflows/Build%20Stage/badge.svg)
![Run Server Tests](https://github.com/josephbmanley/defend-together/workflows/Run%20Server%20Tests/badge.svg)
## Client
# Defend Together
The client is build via Godot v3.2.1
![Header Image](https://static.cloudsumu.com/dt/header.png)
## Server
Defend Together is a mutiplayer demo game project created in C++ with the [ENet Library](http://enet.bespin.org/) and [Godot v3.2.1](https://godotengine.org/)
The server is a C++ app built using ENet
See the [wiki](https://github.com/josephbmanley/defend-together/wiki) for project documentation
Checkout the project on [Itch.io](https://josephbmanley.itch.io/defend-together)
View nested `ReadMe.md` files:
- [Client](blob/master/client)
- [Server](blob/master/server)
---
This project highlights the following:
- Continuous Integration & Continuous Deployment
- UDP Server Networking
- Utilization of Cloud Services in development
## Screenshots
![Screenshot0](https://static.cloudsumu.com/dt/dt-screenshot0.png)

@ -1,4 +1,4 @@
# LD46 Client
# Defend Together Client
## Build Requirements
@ -6,4 +6,4 @@ Godot 3.2.1-stable
### Godot Modules
- [GDNet3](https://github.com/perdugames/gdnet3)
- [GDNet3](https://github.com/josephbmanley/gdnet3)

@ -23,3 +23,87 @@ binary_format/64_bits=true
binary_format/embed_pck=false
custom_template/release="templates/linux"
custom_template/debug="templates/linux"
[preset.1]
name="win32"
platform="Windows Desktop"
runnable=true
custom_features=""
export_filter="all_resources"
include_filter=""
exclude_filter=""
export_path=""
patch_list=PoolStringArray( )
script_export_mode=1
script_encryption_key=""
[preset.1.options]
texture_format/bptc=false
texture_format/s3tc=true
texture_format/etc=false
texture_format/etc2=false
texture_format/no_bptc_fallbacks=true
binary_format/64_bits=false
binary_format/embed_pck=false
custom_template/release="templates/win32"
custom_template/debug="templates/win32"
codesign/enable=false
codesign/identity=""
codesign/password=""
codesign/timestamp=true
codesign/timestamp_server_url=""
codesign/digest_algorithm=1
codesign/description=""
codesign/custom_options=PoolStringArray( )
application/icon=""
application/file_version=""
application/product_version=""
application/company_name=""
application/product_name=""
application/file_description=""
application/copyright=""
application/trademarks=""
[preset.2]
name="win64"
platform="Windows Desktop"
runnable=false
custom_features=""
export_filter="all_resources"
include_filter=""
exclude_filter=""
export_path=""
patch_list=PoolStringArray( )
script_export_mode=1
script_encryption_key=""
[preset.2.options]
texture_format/bptc=false
texture_format/s3tc=true
texture_format/etc=false
texture_format/etc2=false
texture_format/no_bptc_fallbacks=true
binary_format/64_bits=true
binary_format/embed_pck=false
custom_template/release="templates/win64"
custom_template/debug="templates/win64"
codesign/enable=false
codesign/identity=""
codesign/password=""
codesign/timestamp=true
codesign/timestamp_server_url=""
codesign/digest_algorithm=1
codesign/description=""
codesign/custom_options=PoolStringArray( )
application/icon=""
application/file_version=""
application/product_version=""
application/company_name=""
application/product_name=""
application/file_description=""
application/copyright=""
application/trademarks=""

@ -1,7 +1,8 @@
[gd_scene load_steps=6 format=2]
[gd_scene load_steps=7 format=2]
[ext_resource path="res://assets/images/character/iron_player.png" type="Texture" id=1]
[ext_resource path="res://scripts/entities/Player.gd" type="Script" id=2]
[ext_resource path="res://scripts/entities/Camera.gd" type="Script" id=3]
[sub_resource type="RectangleShape2D" id=1]
@ -33,3 +34,7 @@ valign = 1
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Camera" type="Camera2D" parent="."]
zoom = Vector2( 0.5, 0.5 )
script = ExtResource( 3 )

@ -23,9 +23,38 @@ config/icon="res://icon.png"
MusicManager="*res://nodes/MusicManager.tscn"
NetworkManager="*res://nodes/NetworkManager.tscn"
ImportantEntities="*res://scripts/singletons/ImportantEntities.gd"
[input]
ui_left={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777231,"unicode":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":14,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":65,"unicode":0,"echo":false,"script":null)
]
}
ui_right={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777233,"unicode":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":15,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":68,"unicode":0,"echo":false,"script":null)
]
}
ui_up={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777232,"unicode":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":12,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":87,"unicode":0,"echo":false,"script":null)
]
}
ui_down={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777234,"unicode":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":13,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":83,"unicode":0,"echo":false,"script":null)
]
}
send_chat_message={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777221,"unicode":0,"echo":false,"script":null)

Binary file not shown.

Binary file not shown.

@ -0,0 +1,35 @@
extends Camera2D
#-------------#
# Camera Zoom #
#-------------#
export var zoomDecelration = 4
export var zoomCapSpeed = 0.125
export var minZoom = 0.25
export var maxZoom = 0.5
var zoomChange = 0
func _ready():
zoom.x = (minZoom + maxZoom) / 2
zoom.y = (minZoom + maxZoom) / 2
func _process(delta):
CameraZoom(delta)
func _input(event):
if event is InputEventMouseButton:
if event.is_pressed():
if event.button_index == BUTTON_WHEEL_UP:
zoomChange = -zoomCapSpeed
if event.button_index == BUTTON_WHEEL_DOWN:
zoomChange = zoomCapSpeed
func CameraZoom(delta):
if(zoomChange > 0):
zoomChange = clamp(zoomChange - zoomDecelration * delta, 0, zoomCapSpeed)
elif(zoomChange < 0):
zoomChange = clamp(zoomChange + zoomDecelration * delta, -zoomCapSpeed, 0)
zoom.x = clamp(zoom.x + zoomChange, minZoom, maxZoom)
zoom.y = clamp(zoom.y + zoomChange, minZoom, maxZoom)

@ -1,27 +1,53 @@
extends KinematicBody2D
const BASE_SPEED : float = 50.0
export var user : String = ""
var main : bool = false
var input_locks = 0
const SPEED_VAL = 75
func set_main():
main = true
$Label.hide()
$Camera.show()
$Camera.current = true
$"/root/ImportantEntities".main_player = self
func _ready():
$Camera.hide()
set_username("")
func set_username(username):
print(username)
user = username
$Label.text = user
func update_position(pos : Vector2):
if not main or abs(abs(pos.x) - abs(position.x)) > SPEED_VAL or abs(abs(pos.y) - abs(position.y)) > SPEED_VAL:
position = pos
var velocity : Vector2 = Vector2(0,0)
func _process(delta):
var movePos : Vector2 = Vector2(0,0)
if(Input.is_action_just_pressed("ui_up")):
movePos.y = movePos.y - 1
if(Input.is_action_just_pressed("ui_down")):
movePos.y = movePos.y + 1
if(Input.is_action_just_pressed("ui_right")):
movePos.x = movePos.x + 1
if(Input.is_action_just_pressed("ui_left")):
movePos.x = movePos.x - 1
if main and input_locks <= 0:
var relative_movement : Vector2 = Vector2(0,0)
if(Input.is_action_pressed("ui_up")):
relative_movement.y = relative_movement.y - 1
if(Input.is_action_pressed("ui_down")):
relative_movement.y = relative_movement.y + 1
if(Input.is_action_pressed("ui_right")):
relative_movement.x = relative_movement.x + 1
if(Input.is_action_pressed("ui_left")):
relative_movement.x = relative_movement.x - 1
if movePos != Vector2.ZERO:
$"/root/NetworkManager".move_player(movePos.x, movePos.y)
if relative_movement != Vector2.ZERO:
move_and_collide(relative_movement * delta * BASE_SPEED)
var dest = $"/root/ImportantEntities".tile_map.world_to_map(self.position)
dest = dest + (position - (dest * 16))/16
$"/root/NetworkManager".move_player(dest.x, dest.y)
func lock_input():
input_locks = input_locks + 1
func unlock_input():
input_locks = input_locks - 1

@ -23,6 +23,7 @@ var packetQueue = []
var error_info = ""
var world_data : String = ""
var last_move_time = null
var connection_timer : Timer
@ -107,19 +108,22 @@ func send_chat_message(msg : String):
var pckt : PoolByteArray = (msg + '\n').to_ascii()
send_packet(pckt, 1)
func send_packet(packet : PoolByteArray, channel = 0):
packetQueue.append({'channel':channel, 'packet' : packet})
func send_packet(packet : PoolByteArray, channel = 0, pck_type = GDNetMessage.RELIABLE):
packetQueue.append({'channel':channel, 'packet' : packet, 'type' : pck_type})
func move_player(x,y):
var pckt : PoolByteArray = ("3|" + str(x) + "," + str(y)).to_ascii()
if last_move_time == null || OS.get_ticks_msec() - last_move_time > 50:
var pckt : PoolByteArray = ("3|" + str(x) + "," + str(y)).to_ascii()
send_packet(pckt)
send_packet(pckt, 0, GDNetMessage.SEQUENCED)
last_move_time = OS.get_ticks_msec()
func _process(delta):
process_events()
if len(packetQueue) > 0 and connected:
peer.send_packet(packetQueue[0]['packet'], packetQueue[0]['channel'], GDNetMessage.RELIABLE)
peer.send_packet(packetQueue[0]['packet'], packetQueue[0]['channel'], packetQueue[0]['type'])
packetQueue.remove(0)

@ -0,0 +1,4 @@
extends Node
var main_player : KinematicBody2D = null
var tile_map : TileMap = null

@ -2,6 +2,7 @@ extends Node
func _ready():
$"/root/ImportantEntities".tile_map = $Tiles
$Tiles.clear()
$"/root/NetworkManager".connect("world_data_recieved", self, "_on_world_update")
$"/root/NetworkManager".request_world_map()
@ -16,24 +17,22 @@ func _on_world_update():
elif ',' in tileUpdate:
var tile_data = tileUpdate.split(',')
if ':' in tile_data[2]:
var pos : Vector2 = $Tiles.map_to_world(Vector2(int(tile_data[0]), int(tile_data[1])))
if 'player:' in tile_data[2]:
print(tile_data)
var player_name = tile_data[2].substr(len('player:'))
print(player_name)
if $"/root/NetworkManager".username == player_name:
$Player.position = pos
else:
update_entity(player_name, pos, "player")
else:
var entity_data = tile_data[2].split(':')
update_entity(entity_data[1], pos, entity_data[0])
var pos : Vector2 = $Tiles.map_to_world(Vector2(float(tile_data[0]), float(tile_data[1])))
pos = Vector2(pos.x + get_decimals(float(tile_data[0])), pos.y + get_decimals(float(tile_data[1])))
#pos = Vector2(pos.x + decs.x, pos.y + decs.y)
var entity_data = tile_data[2].split(':')
update_entity(entity_data[1], pos, entity_data[0])
else:
$Tiles.set_cell(int(tile_data[0]), int(tile_data[1]), int(tile_data[2]))
if $Loading != null:
if get_node_or_null("Loading") != null:
$Loading.queue_free()
func get_decimals(number : float):
var temp : float = int(number)
return (number - temp) * 16
func display_error(error):
print("Error " + error)
@ -46,10 +45,16 @@ func update_entity(entity_id : String, pos : Vector2, type : String):
entity = gobj.instance()
add_child(entity, true)
entity.set_name(str(type + "-" + entity_id))
if type == "player":
if entity_id == $"/root/NetworkManager".username:
entity.set_main()
else:
display_error("Trying to load entity of type: " + type + ", but failed.")
if entity:
entity.position = pos
if entity.has_method("update_position"):
entity.update_position(pos)
else:
entity.position = pos
if entity.has_method("set_username"):
entity.set_username(entity_id)

@ -0,0 +1,37 @@
extends Control
const MESSAGE_TIMEOUT : int = 17
var locking = false
func _ready():
$"/root/NetworkManager".connect("chat_message_recieved", self, "_on_new_message")
func _on_new_message(message):
print(message)
var message_block : Label = Label.new()
message_block.text = message
var timer = Timer.new()
timer.connect("timeout", message_block, "queue_free")
message_block.add_child(timer)
$Messages.add_child(message_block)
timer.start(MESSAGE_TIMEOUT)
func _process(delta):
if $LineEdit.has_focus() != locking:
locking = $LineEdit.has_focus()
if locking:
$"/root/ImportantEntities".main_player.lock_input()
else:
$"/root/ImportantEntities".main_player.unlock_input()
if(Input.is_action_just_pressed("send_chat_message")):
if $LineEdit.has_focus():
if len($LineEdit.text) > 0:
$"/root/NetworkManager".send_chat_message($LineEdit.text)
$LineEdit.text = ""
$LineEdit.release_focus()
else:
$LineEdit.grab_focus()

@ -1 +1 @@
Subproject commit a7d7961c271839c625718e42f7ac8d6171456892
Subproject commit 2826c2eb036860bf7269d34b01aa24a9501752d4

@ -39,7 +39,7 @@ Parameters:
SubDomain:
Type: String
Description: The subdomain to be used by dt. (ex. `dt.example.com`)
Default: dt
Default: prod
#------------
# CloudWatch
@ -61,7 +61,7 @@ Resources:
Condition: CreateDns
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Sub 'https://s3.${AWS::Region}.amazonaws.com/sumu-stacks/dt/${release}/cloudformation/dt/dns.yaml'
TemplateURL: !Sub 'https://s3.${AWS::Region}.amazonaws.com/dt-deployment-bucket/${release}/cloudformation/dt/dns.yaml'
Parameters:
environment: !Ref environment
Domain: !Ref Domain
@ -74,7 +74,7 @@ Resources:
LoadBalancing:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Sub 'https://s3.${AWS::Region}.amazonaws.com/sumu-stacks/dt/${release}/cloudformation/dt/load_balancing.yaml'
TemplateURL: !Sub 'https://s3.${AWS::Region}.amazonaws.com/dt-deployment-bucket/${release}/cloudformation/dt/load_balancing.yaml'
Parameters:
environment: !Ref environment
release: !Ref release
@ -84,7 +84,7 @@ Resources:
EcsCluster:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Sub 'https://s3.${AWS::Region}.amazonaws.com/sumu-stacks/dt/${release}/cloudformation/cluster/top.yaml'
TemplateURL: !Sub 'https://s3.${AWS::Region}.amazonaws.com/dt-deployment-bucket/${release}/cloudformation/cluster/top.yaml'
Parameters:
Environment: !Ref environment
VpcId: !Ref VpcId
@ -97,7 +97,7 @@ Resources:
TaskDefinition:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Sub 'https://s3.${AWS::Region}.amazonaws.com/sumu-stacks/dt/${release}/cloudformation/dt/task.yaml'
TemplateURL: !Sub 'https://s3.${AWS::Region}.amazonaws.com/dt-deployment-bucket/${release}/cloudformation/dt/task.yaml'
Parameters:
environment: !Ref environment
LogGroupName: !Ref LogGroup

@ -1,16 +1,31 @@
# LD46 Server
# Defend Together Server
Here are the instructions below for running the server locally on linux.
## Build Requirements
Enet development files:
_Instructions are for Fedora Linux_
`dnf install enet-devel`
Install dependencies:
G++
`dnf install g++`
`dnf install enet-devel g++`
For testing:
`dnf install gtest gmock gmock-devel gtest-devel`
`dnf install gtest gmock gmock-devel gtest-devel`
## Building Server
Within the server directory run: `bash build.sh`
## Running Tests
Within the server directory run: `bash run_tests.sh`
## Running Server
If you have already built the server, you can run the binary location at `builds/server.out`, otherwise within the server directory, you can run `run_build.sh` to build and run the server.
# Running in Docker
A `Dockerfile` is provided in this project. So you can run `docker build -t defend-together .` and then `docker run -p 7777:7777 defend-together` to run locally on port `7777` in docker.

@ -5,9 +5,7 @@
#include <iostream>
#include <string>
#include <cstring>
bool console_is_running = false;
#include "gameserver.hpp"
class ServerConsole
{
@ -26,17 +24,33 @@ void * console_logic(void *)
std::string input_string;
while(console_is_running)
while(game_is_running)
{
if(std::getline(std::cin, input_string))
{
if(input_string == "stop")
{
console_is_running = false;
game_is_running = false;
}
else if (input_string.length() > 2 && input_string.substr(0,3) == "say")
{
if(input_string.length() > 4)
{
gameserver::BroadcastMessage("Server: " + input_string.substr(4));
}
else
{
std::cout << "Must pass a valid message!" << std::endl;
}
}
else
{
std::cout << "Invalid console command!" << std::endl;
std::cout << std::endl
<< "Invalid console command!" << std::endl
<< "Valid commands are:" << std::endl
<< "stop" << std::endl
<< "say [message]" << std::endl;
}
}
}
@ -46,7 +60,7 @@ void * console_logic(void *)
ServerConsole::ServerConsole(void)
{
//Intialized values
console_is_running = true;
game_is_running = true;
thread = pthread_t();
@ -64,7 +78,7 @@ void ServerConsole::stop(void)
bool ServerConsole::is_running(void)
{
return console_is_running;
return game_is_running;
}
#endif

@ -7,38 +7,60 @@
class GameEntity
{
public:
GameEntity(std::string,std::string,int,int);
GameEntity(std::string,std::string,float,float);
std::string get_id(void);
std::string get_type(void);
int get_x(void);
int get_y(void);
std::string set_x(int);
std::string set_y(int);
float get_x(void);
float get_y(void);
std::string set_x(float);
std::string set_y(float);
void set_velocity(float, float);
std::string get_dump(void);
bool movement_tick(void);
private:
int pos_x;
int pos_y;
float pos_x;
float pos_y;
float velocity_x;
float velocity_y;
std::string id;
std::string type;
};
GameEntity::GameEntity(std::string entity_id, std::string entity_type, int x = 0, int y = 0)
GameEntity::GameEntity(std::string entity_id, std::string entity_type, float x = 0, float y = 0)
{
id = entity_id;
type = entity_type;
velocity_x = 0;
velocity_y = 0;
this->set_x(x);
this->set_y(y);
std::cout << "Entity created with id: '" << id << "' at " << x << "," << y << std::endl;
}
bool GameEntity::movement_tick()
{
if(velocity_x != 0 || velocity_y != 0)
{
set_x(pos_x + velocity_x);
set_y(pos_y + velocity_y);
return true;
}
return false;
}
int GameEntity::get_x(void)
void GameEntity::set_velocity(float x, float y)
{
velocity_x = x;
velocity_y = y;
}
float GameEntity::get_x(void)
{
return pos_x;
}
int GameEntity::get_y(void)
float GameEntity::get_y(void)
{
return pos_y;
}
@ -48,12 +70,12 @@ std::string GameEntity::get_dump(void)
return std::to_string(pos_x) + "," + std::to_string(pos_y) + "," + type + ":" + id + '\n';
}
std::string GameEntity::set_x(int x)
std::string GameEntity::set_x(float x)
{
pos_x = x;
return this -> get_dump();
}
std::string GameEntity::set_y(int y)
std::string GameEntity::set_y(float y)
{
pos_y = y;
return this -> get_dump();

@ -4,6 +4,7 @@
#include <string>
#include <iostream>
#include <vector>
#include <sstream>
#include "gameentity.hpp"
@ -15,14 +16,17 @@ class GameMap
void set_tile(int,int,int);
std::string get_tile_dump(void);
std::string get_entity_dump(void);
std::string spawn_entity(std::string,std::string,int,int);
std::string move_entity(std::string,std::string,int,int);
std::string spawn_entity(std::string,std::string,float,float);
std::string move_entity(std::string,std::string,float,float);
void set_entity_velocity(std::string,std::string,float,float);
std::string move_enitity_relatively(std::string,std::string,float,float);
int get_entity_pos_x(std::string,std::string);
int get_entity_pos_y(std::string,std::string);
int get_size_x(void);
int get_size_y(void);
bool entity_exists(std::string, std::string);
bool remove_entity(std::string, std::string);
std::string world_tick(void);
private:
int ** map_data;
@ -58,14 +62,26 @@ GameMap::GameMap(int x, int y)
}
std::string GameMap::spawn_entity(std::string entity_id, std::string entity_type, int x = 0, int y = 0)
std::string GameMap::spawn_entity(std::string entity_id, std::string entity_type, float x = 0, float y = 0)
{
GameEntity entity = GameEntity(entity_id, entity_type, x, y);
entities.push_back(entity);
return entity.get_dump();
}
std::string GameMap::move_entity(std::string entity_id, std::string entity_type, int x = 0, int y = 0)
std::string GameMap::move_enitity_relatively(std::string entity_id, std::string entity_type, float x = 0, float y = 0)
{
for(int i = 0; i < entities.size(); i++)
{
if(entities[i].get_id() == entity_id && entities[i].get_type() == entity_type)
{
return move_entity(entity_id, entity_type, x - entities[i].get_x(), y - entities[i].get_y());
}
}
return "";
}
std::string GameMap::move_entity(std::string entity_id, std::string entity_type, float x = 0, float y = 0)
{
for(int i = 0; i < entities.size(); i++)
{
@ -106,6 +122,18 @@ std::string GameMap::move_entity(std::string entity_id, std::string entity_type,
return "";
}
void GameMap::set_entity_velocity(std::string entity_id, std::string entity_type, float x = 0, float y = 0)
{
for(int i = 0; i < entities.size(); i++)
{
if(entities[i].get_id() == entity_id && entities[i].get_type() == entity_type)
{
GameEntity* entity = &entities[i];
entity->set_velocity(x,y);
}
}
}
bool GameMap::valid_x_y(int x, int y)
{
if(x < size_x && x >= 0)
@ -118,6 +146,23 @@ bool GameMap::valid_x_y(int x, int y)
return false;
}
std::string GameMap::world_tick(void)
{
std::string dump = "";
for(int i = 0; i < entities.size(); i++)
{
if(entities[i].movement_tick())
{
dump += entities[i].get_dump();
}
}
if(dump != "")
{
std::cout << dump << std::endl;
}
return dump;
}
int GameMap::get_tile(int x, int y)
{
if(this->valid_x_y(x,y))
@ -214,7 +259,6 @@ bool GameMap::remove_entity(std::string entity_id, std::string entity_type)
return false;
}
int GameMap::get_entity_pos_x(std::string entity_id, std::string entity_type)
{
for(int i = 0; i < entities.size(); i++)

@ -7,7 +7,13 @@
#include <sstream>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include "gamemap.hpp"
using namespace std;
bool game_is_running = false;
const int SERVER_PORT = 7777;
const int MAX_PLAYERS = 32;
@ -21,86 +27,202 @@ static std::string usernames[MAX_PLAYERS];
static ENetHost* server;
//===================
// Network Processes
// Game Server Logic
//===================
void PlayerMove(ENetEvent* event)
namespace gameserver
{
int peer_id = event->peer -> incomingPeerID;
int move_x = 0;
int move_y = 1;
std::string data_input((char*)event->packet->data);
data_input = data_input.substr(2);
//Parse input string
std::stringstream ss(data_input);
std::string tempString;
std::getline(ss, tempString, ',');
move_x = std::stoi(tempString);
std::getline(ss, tempString, '\n');
move_y = std::stoi(tempString);
//Update player position
std::string resp = "2|" + gamemap.move_entity(usernames[peer_id], "player", move_x, move_y);
const char* data = resp.c_str();
ENetPacket* packet = enet_packet_create(data, strlen(data) + 1, ENET_PACKET_FLAG_RELIABLE);
enet_host_broadcast(server, 0, packet);
}
void DataRequest(ENetEvent* event)
{
//Create world data dump
std::string map_dump_resp("2|");
map_dump_resp += gamemap.get_tile_dump() + gamemap.get_entity_dump();
const char* data = map_dump_resp.c_str();
//Build and send reliable packet to requester
ENetPacket* packet = enet_packet_create(data, strlen(data) + 1, ENET_PACKET_FLAG_RELIABLE);
enet_peer_send(event->peer, 0, packet);
}
void Authenticate(ENetEvent* event)
{
//Grab username from packet data
std::string username((char*)event->packet->data);
username = username.substr(2);
//Determine if peer already has username
int peer_id = event -> peer -> incomingPeerID;
const char* data;
if(usernames[peer_id] == "")
void Intialize()
{
//Update username array
usernames[peer_id] = username;
printf("Starting DT game server...\n");
//Spawn entity
std::string spawn_data = gamemap.spawn_entity(usernames[peer_id], "player", 127, 127);
//Tell peers about new player
data = ("2|" + spawn_data).c_str();
ENetPacket* packet = enet_packet_create(data, strlen(data) + 1, ENET_PACKET_FLAG_RELIABLE);
enet_host_broadcast(server, 0, packet);
std::cout << usernames[peer_id] << " spawned!" << std::endl;
std::cout << peer_id << " | " << username << std::endl;
std::string okay_response("1|OK");
data = (okay_response + username).c_str();
//Return success
packet = enet_packet_create(data, strlen(data) + 1, ENET_PACKET_FLAG_RELIABLE);
enet_peer_send(event -> peer, 0, packet);
for(int i = 0; i < MAX_PLAYERS; i++)
{
usernames[i] = "";
}
}
else
void PlayerMove(ENetEvent* event)
{
std::string resp("1|Already logged in!");
data = resp.c_str();
//Return reliable error message
int peer_id = event->peer -> incomingPeerID;
float move_x = 0;
float move_y = 0;
//Remove id marker from packet data
std::string data_input((char*)event->packet->data);
data_input = data_input.substr(2);
//Parse input string
std::stringstream ss(data_input);
std::string tempString;
std::getline(ss, tempString, ',');
move_x = std::stof(tempString);
std::getline(ss, tempString, '\n');
move_y = std::stof(tempString);
//Update player position
std::string resp = "2|" + gamemap.move_enitity_relatively(usernames[peer_id], "player", move_x, move_y);
const char* data = resp.c_str();
ENetPacket* packet = enet_packet_create(data, strlen(data) + 1, 0);
enet_host_broadcast(server, 0, packet);
}
void DataRequest(ENetEvent* event)
{
//Create world data dump
std::string map_dump_resp("2|");
map_dump_resp += gamemap.get_tile_dump() + gamemap.get_entity_dump();
const char* data = map_dump_resp.c_str();
//Build and send reliable packet to requester
ENetPacket* packet = enet_packet_create(data, strlen(data) + 1, ENET_PACKET_FLAG_RELIABLE);
enet_peer_send(event -> peer, 0, packet);
enet_peer_send(event->peer, 0, packet);
}
void Authenticate(ENetEvent* event)
{
//Grab username from packet data
std::string username((char*)event->packet->data);
username = username.substr(2);
//Determine if peer already has username
int peer_id = event -> peer -> incomingPeerID;
const char* data;
if(usernames[peer_id] == "")
{
//Update username array
usernames[peer_id] = username;
//Spawn entity
std::string spawn_data = gamemap.spawn_entity(usernames[peer_id], "player", 128, 129);
//Tell peers about new player
data = ("2|" + spawn_data).c_str();
ENetPacket* packet = enet_packet_create(data, strlen(data) + 1, ENET_PACKET_FLAG_RELIABLE);
enet_host_broadcast(server, 0, packet);
std::cout << "'" << usernames[peer_id] << "' spawned!" << std::endl;
std::string okay_response("1|OK");
data = (okay_response + username).c_str();
//Return success
packet = enet_packet_create(data, strlen(data) + 1, ENET_PACKET_FLAG_RELIABLE);
enet_peer_send(event -> peer, 0, packet);
}
else
{
std::string resp("1|Already logged in!");
data = resp.c_str();
//Return reliable error message
ENetPacket* packet = enet_packet_create(data, strlen(data) + 1, ENET_PACKET_FLAG_RELIABLE);
enet_peer_send(event -> peer, 0, packet);
}
}
void SendPlayerMessage(ENetPeer* peer, std::string message)
{
const char* data = message.c_str();
ENetPacket* packet = enet_packet_create(data, strlen(data) + 1, ENET_PACKET_FLAG_RELIABLE);
enet_peer_send(peer, 1, packet);
}
bool SendPlayerMessage(std::string username, std::string message)
{
for(int i = 0; i < MAX_PLAYERS; i++)
{
if(usernames[i] == username)
{
SendPlayerMessage(&(server->peers[i]), message);
return true;
}
}
return false;
}
void BroadcastMessage(std::string message)
{
const char* data = message.c_str();
std::cout << data << std::endl;
ENetPacket* packet = enet_packet_create(data, strlen(data) + 1, ENET_PACKET_FLAG_RELIABLE);
enet_host_broadcast(server, 1, packet);
}
void ProcessChatMessage(ENetEvent* event)
{
int peer_id = event->peer -> incomingPeerID;
if(usernames[peer_id] != "")
{
//Parse input string
std::stringstream ss((char*)event->packet->data);
std::string chat_message;
std::getline(ss, chat_message, '\n');
std::string resp = "<" + usernames[peer_id] + "> " + chat_message;
BroadcastMessage(resp);
}
}
void HandlePlayerDisconnect(ENetEvent* event)
{
//Clear username data and remove entity
std::string username = usernames[event -> peer -> incomingPeerID];
if(username != "")
{
std::cout << "'" << username << "' disconnected" << std::endl;
gamemap.remove_entity(username,"player");
usernames[event -> peer -> incomingPeerID] = "";
std::string resp = "2|delete,player:" + username;
const char* data = resp.c_str();
ENetPacket* packet = enet_packet_create(data, strlen(data) + 1, ENET_PACKET_FLAG_RELIABLE);
enet_host_broadcast(server, 0, packet);
}
}
void ProcessGeneralInput(ENetEvent* event)
{
char c [1];
c[0] = event->packet->data[0];
switch (c[0])
{
case '1':
gameserver::Authenticate(event);
break;
case '2':
gameserver::DataRequest(event);
break;
case '3':
gameserver::PlayerMove(event);
break;
default:
std::cout << "Invalid packet recieved!" <<std::endl;
break;
}
}
pthread_t ticker_thread;
void * GameTicker(void *)
{
while(game_is_running)
{
usleep(50000*3);
std::string data_string = "2|"+gamemap.world_tick();
const char* data = data_string.c_str();
ENetPacket* packet = enet_packet_create(data, strlen(data), ENET_PACKET_FLAG_UNSEQUENCED);
enet_host_broadcast(server, 0, packet);
}
return 0;
}
void StartTicker()
{
pthread_create(&ticker_thread, NULL, GameTicker, NULL);
}
}

@ -9,17 +9,11 @@
#include "console.hpp"
#include "gameserver.hpp"
using namespace std;
int main (int argc, char ** argv)
{
printf("Starting LD46 game server...\n");
for(int i = 0; i < MAX_PLAYERS; i++)
{
usernames[i] = "";
}
//Intialize Enet
if (enet_initialize () != 0)
{
fprintf (stderr, "ENet server failed to initialize!\n");
@ -27,35 +21,37 @@ int main (int argc, char ** argv)
}
atexit (enet_deinitialize);
//Intialize game data
gameserver::Intialize();
//Create console
ServerConsole console;
//Create ENet objects
ENetEvent event;
ENetAddress address;
//Build Enet Host
address.host = ENET_HOST_ANY;
address.port = SERVER_PORT;
const int CHANNEL_COUNT = 2;
server = enet_host_create (&address, MAX_PLAYERS, CHANNEL_COUNT, 0, 0);
if (server == NULL)
{
std::cout << "Failed to create ENet server host!" << std::endl;
return 1;
}
//Start ticker
gameserver::StartTicker();
//Run main game server loop
std::cout << "Awaiting connections..." << std::endl;
while(console_is_running)
while(game_is_running)
{
ENetEvent event;
/* Wait up to 1000 milliseconds for an event. */
while (enet_host_service (server, & event, EVENT_RATE) > 0)
{
printf("Parsing event!\n");
switch (event.type)
{
case ENET_EVENT_TYPE_CONNECT:
@ -64,73 +60,24 @@ int main (int argc, char ** argv)
break;
case ENET_EVENT_TYPE_RECEIVE:
std::cout << "A packet of length " << event.packet -> dataLength << " containing "
<< event.packet -> data << " was received from " << event.peer -> data << " on channel"
<< event.channelID << std::endl;
//Channel #0 processes authentication, world updates,
// and player movement
if (event.channelID == 0)
{
char c [1];
c[0] = event.packet->data[0];
/* Determine which function to call based on
the value of the first character of packet */
std::cout << "Packet Type: " << c << std::endl;
switch (c[0])
{
case '1':
Authenticate(&event);
break;
case '2':
DataRequest(&event);
break;
case '3':
PlayerMove(&event);
break;
default:
std::cout << "Invalid packet recieved!" <<std::endl;
break;
}
enet_packet_destroy (event.packet);
gameserver::ProcessGeneralInput(&event);
}
//Channel #1 proccesses chat messages
else if(event.channelID == 1)
{
int peer_id = event.peer -> incomingPeerID;
if(usernames[peer_id] != "")
{
//Parse input string
std::stringstream ss((char*)event.packet->data);
std::string chat_message;
std::getline(ss, chat_message, '\n');
std::string resp = "<" + usernames[peer_id] + "> " + chat_message;
const char* data = resp.c_str();
std::cout << data << std::endl;
ENetPacket* packet = enet_packet_create(data, strlen(data) + 1, ENET_PACKET_FLAG_RELIABLE);
enet_host_broadcast(server, 1, packet);
}
enet_packet_destroy (event.packet);
gameserver::ProcessChatMessage(&event);
}
enet_packet_destroy (event.packet);
break;
case ENET_EVENT_TYPE_DISCONNECT:
std::cout << event.peer -> data << " disconnected." << std::endl;
//Clear username data and remove entity
std::string username = usernames[event.peer -> incomingPeerID];
if(username != "")
{
std::cout << "Removing '" << username << "'s player data!" << std::endl;
gamemap.remove_entity(username,"player");
usernames[event.peer -> incomingPeerID] = "";
std::string resp = "2|delete,player:" + username;
const char* data = resp.c_str();
ENetPacket* packet = enet_packet_create(data, strlen(data) + 1, ENET_PACKET_FLAG_RELIABLE);
enet_host_broadcast(server, 0, packet);
}
gameserver::HandlePlayerDisconnect(&event);
//Open peer for new connection
event.peer -> data = NULL;

@ -39,12 +39,12 @@ TEST(EntityTest, GetBasicDump)
{
GameEntity entity = CreateTestEntity();
//Test intial location (0,0)
EXPECT_EQ(entity.get_dump(), std::string("0,0," + TEST_ENTITY_TYPE + ":" + TEST_ENTITY_ID + "\n"));
EXPECT_EQ(entity.get_dump(), std::string("0.000000,0.000000," + TEST_ENTITY_TYPE + ":" + TEST_ENTITY_ID + "\n"));
//Test non-intial location (1,2)
entity.set_x(1);
entity.set_y(2);
EXPECT_EQ(entity.get_dump(), std::string("1,2," + TEST_ENTITY_TYPE + ":" + TEST_ENTITY_ID + "\n"));
EXPECT_EQ(entity.get_dump(), std::string("1.000000,2.000000," + TEST_ENTITY_TYPE + ":" + TEST_ENTITY_ID + "\n"));
}
//Test GameMap Object