35 Commits

Author SHA1 Message Date
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
949549caff Include itch.io values 2020-05-03 22:30:01 -04:00
3b5ca90cfb Implement Client CI/CD
Enable debug export

Push build dir instead of artifact

Lots of debugging stuff

Use debug export branch

Client pipeline debugging...

Client pipeline debugging...

Client pipeline debugging...

Client pipeline debugging...

Client pipeline debugging...

Client pipeline debugging...

Client pipeline debugging...

Client pipeline debugging...

Client pipeline debugging...

Client pipeline debugging...

Client pipeline debugging...

Cleanup and publish
2020-05-03 22:22:32 -04:00
95d52a8866 Client-side chat changes 2020-05-03 19:35:58 -04:00
3b9e5d2ece Debug chat issues 2020-05-03 19:34:07 -04:00
9145747e20 Implemented basic chat system 2020-05-02 17:25:31 -04:00
ee233c52f0 Added client nameplates 2020-05-02 16:36:51 -04:00
948b379f9d Client environment selection dropdown 2020-05-02 16:19:46 -04:00
ddf5275a37 Fix 2020-05-02 03:20:49 -04:00
ce9afe339c Enable dynamic port routing 2020-05-02 03:12:12 -04:00
fee56c6042 Dockerfile with working healthcheck endpoint 2020-05-02 03:02:06 -04:00
e2a8f446bb Merge 2020-05-02 01:03:29 -04:00
bec4209232 Disable dynamic routing to allow for proper healthchecks 2020-05-02 01:02:28 -04:00
b93240f992 Configure endpoint for healthcheck
Configure endpoint for healthcheck

Update healthcheck to run

HOTFIX: Fix healthcheck protocol

HOTFIX: Expose healthcheck port task

HOTFIX: Make healthcheck UDP & remove TCP endpoint

Reimplement healthcheck without forcing port

Reimplement healthcheck without forcing port
2020-05-02 00:50:16 -04:00
31831ff9d1 Reimplement healthcheck without forcing port 2020-05-02 00:31:34 -04:00
db37a8c924 Reimplement healthcheck without forcing port 2020-05-02 00:16:51 -04:00
11a41fdca7 HOTFIX: Make healthcheck UDP & remove TCP endpoint 2020-05-02 00:02:21 -04:00
f4e1d3a3b8 HOTFIX: Expose healthcheck port task 2020-05-01 23:57:09 -04:00
4caab69266 HOTFIX: Fix healthcheck protocol 2020-05-01 23:52:20 -04:00
15f6a8df22 Update healthcheck to run 2020-05-01 23:45:13 -04:00
fb236647a3 Configure endpoint for healthcheck 2020-05-01 17:43:26 -04:00
5fbbfba593 Configure endpoint for healthcheck 2020-05-01 17:42:59 -04:00
84f6ca7434 Client entity deletion 2020-05-01 02:27:47 -04:00
9a6ba77e72 Additional testing, entity deletion, and player disconnection 2020-05-01 02:24:34 -04:00
b94b469c39 INFRASTRUCTURE FIX: Container port 'tcp' -> 'udp' 2020-05-01 00:15:22 -04:00
6eaa057da8 Rework console input to use iostream 2020-05-01 00:12:57 -04:00
7ca5c63131 Reduce memory reservation 2020-05-01 00:06:06 -04:00
70b8236f4e Implement stage 2020-04-30 23:47:00 -04:00
27 changed files with 958 additions and 237 deletions

90
.github/workflows/build_stage.yml vendored Normal file
View File

@ -0,0 +1,90 @@
name: Build Stage
on:
push:
branches:
- master
jobs:
godot:
runs-on: ubuntu-latest
strategy:
matrix:
platform: [linux, win32, win64]
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Download Export Template
env:
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
- 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${{ env.EXPORT_SUFFIX }}
preset: ${{ matrix.platform }}
projectDir: client
debugMode: 'true'
- name: Upload Artifact
uses: actions/upload-artifact@v2
with:
name: Client - ${{ matrix.platform }}
path: ${{ github.workspace }}/${{ steps.build.outputs.build }}
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v1
- name: Push Tag to Docker Hub
uses: opspresso/action-docker@master
with:
args: --docker
env:
USERNAME: ${{ secrets.DOCKER_USERNAME }}
PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
BUILD_PATH: "server"
DOCKERFILE: "server/Dockerfile"
IMAGE_NAME: "josephbmanley/defend-together"
TAG_NAME: "stage"
LATEST: "false"
cloudformation:
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v2.1.0
with:
submodules: 'true'
- name: Ship to S3
uses: jakejarvis/s3-sync-action@master
with:
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_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- 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 }}

View File

@ -1,4 +1,4 @@
name: Push Dev Infrastructure
name: Push Dev
on:
push:

View File

@ -1,4 +1,4 @@
name: Release Server
name: Publish Release
on:
release:
@ -6,6 +6,51 @@ on:
- created
jobs:
godot:
runs-on: ubuntu-latest
strategy:
matrix:
platform: [linux, win32, win64]
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Download Export Template
env:
PLATFORM: ${{ matrix.platform }}
run: |
mkdir -p client/templates
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${{ env.EXPORT_SUFFIX }}
preset: ${{ matrix.platform }}
projectDir: client
- id: get_tag
name: Get Tag
env:
GITHUB_HEAD_REF: $${{ github.head_ref }}
GITHUB_BASE_REF: ${{ github.base_ref }}
run: |
TAG=$(jq --raw-output '.release.tag_name' $GITHUB_EVENT_PATH)
echo ::set-output name=TAG::$TAG
- name: Push to Itch
uses: josephbmanley/butler-publish-itchio-action@v1.0.1
env:
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
CHANNEL: ${{ matrix.platform }}
ITCH_GAME: defend-together
ITCH_USER: josephbmanley
PACKAGE: ${{ github.workspace }}/${{ steps.build.outputs.build }}
VERSION: ${{ steps.get_tag.outputs.TAG }}
docker:
runs-on: ubuntu-latest
steps:

3
client/.gitignore vendored
View File

@ -1 +1,2 @@
builds
builds
templates

View File

@ -1,6 +1,6 @@
[preset.0]
name="Linux/X11"
name="linux"
platform="Linux/X11"
runnable=true
custom_features=""
@ -21,28 +21,89 @@ texture_format/etc2=false
texture_format/no_bptc_fallbacks=true
binary_format/64_bits=true
binary_format/embed_pck=false
custom_template/release="/var/home/vetra/Projects/godot-enet/bin/godot.x11.opt.64"
custom_template/debug="/var/home/vetra/Projects/godot-enet/bin/godot.x11.opt.debug.64"
custom_template/release="templates/linux"
custom_template/debug="templates/linux"
[preset.1]
name="HTML5"
platform="HTML5"
name="win32"
platform="Windows Desktop"
runnable=true
custom_features=""
export_filter="all_resources"
include_filter=""
exclude_filter=""
export_path="builds/client.html"
export_path=""
patch_list=PoolStringArray( )
script_export_mode=1
script_encryption_key=""
[preset.1.options]
vram_texture_compression/for_desktop=true
vram_texture_compression/for_mobile=false
html/custom_html_shell=""
html/head_include=""
custom_template/release=""
custom_template/debug=""
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=""

View File

@ -33,3 +33,6 @@ valign = 1
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Camera" type="Camera2D" parent="."]
zoom = Vector2( 0.5, 0.5 )

View File

@ -23,6 +23,43 @@ 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)
]
}
[rendering]

Binary file not shown.

Binary file not shown.

View File

@ -1,23 +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):
user = username
$Label.text = user
#$Label.text = $"/root/NetworkManager".username
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

View File

@ -1,6 +1,7 @@
extends Node
const SERVER_HOST : String = "192.168.1.34"
var server_host : String = "127.0.0.1"
const SERVER_PORT : int = 7777
signal disconnection
@ -9,6 +10,7 @@ signal error_occured
signal logged_in
signal world_data_recieved
signal chat_message_recieved
var username : String = ""
@ -27,15 +29,17 @@ var connection_timer : Timer
func _init():
server_address = GDNetAddress.new()
server_address.set_host(SERVER_HOST)
server_address.set_port(SERVER_PORT)
client = GDNetHost.new()
client.set_max_channels(2)
client.set_max_peers(1)
client.set_event_wait(250)
client.bind()
func connect_to_server():
if peer:
peer.disconnect_now()
server_address.set_host(server_host)
server_address.set_port(SERVER_PORT)
peer = client.host_connect(server_address)
if not connection_timer:
connection_timer = Timer.new()
@ -50,13 +54,13 @@ func timeout_check():
func request_world_map():
var request_packet : PoolByteArray = "2|".to_ascii()
packetQueue.append(request_packet)
send_packet(request_packet)
func connect_as_user(username : String):
connect_to_server()
var username_packet : PoolByteArray = ("1|" + username).to_ascii()
packetQueue.append(username_packet)
send_packet(username_packet)
func display_error(error = "Unknown error occured!"):
error_info = error
@ -73,18 +77,22 @@ func process_events():
var event_type = event.get_event_type()
if(event_type == GDNetEvent.RECEIVE):
var ascii_data : String = str(event.get_packet().get_string_from_ascii())
if len(ascii_data) > 0:
if ascii_data[0] == '1':
if ascii_data.substr(2,2) == "OK":
username = ascii_data.substr(4)
print("Logged in as: " + username)
emit_signal("logged_in")
else:
display_error("Username not accepted! Reason: " + ascii_data.substr(2))
elif ascii_data[0] == '2':
world_data = ascii_data.substr(2)
emit_signal("world_data_recieved")
if event.get_channel_id() == 0:
var ascii_data : String = str(event.get_packet().get_string_from_ascii())
if len(ascii_data) > 0:
if ascii_data[0] == '1':
if ascii_data.substr(2,2) == "OK":
username = ascii_data.substr(4)
print("Logged in as: " + username)
emit_signal("logged_in")
else:
display_error("Username not accepted! Reason: " + ascii_data.substr(2))
elif ascii_data[0] == '2':
world_data = ascii_data.substr(2)
emit_signal("world_data_recieved")
elif event.get_channel_id() == 1:
var chat_message : String = str(event.get_packet().get_string_from_ascii())
emit_signal("chat_message_recieved", chat_message)
elif(event_type == GDNetEvent.CONNECT):
print("Connected to server with hostname: " + server_address.get_host() + ":" + str(server_address.get_port()))
connected = true
@ -94,16 +102,24 @@ func process_events():
connected = false
emit_signal("disconnection")
func send_chat_message(msg : String):
var pckt : PoolByteArray = (msg + '\n').to_ascii()
send_packet(pckt, 1)
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()
packetQueue.append(pckt)
send_packet(pckt, 0, GDNetMessage.SEQUENCED)
func _process(delta):
process_events()
if len(packetQueue) > 0 and connected:
peer.send_packet(packetQueue[0], 0, GDNetMessage.RELIABLE)
peer.send_packet(packetQueue[0]['packet'], packetQueue[0]['channel'], packetQueue[0]['type'])
packetQueue.remove(0)

View File

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

View File

@ -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()
@ -10,27 +11,28 @@ func _on_world_update():
var data = $"/root/NetworkManager".world_data.split('\n')
for tileUpdate in data:
if len(tileUpdate) > 3:
if ',' in tileUpdate:
if "delete," in tileUpdate:
var delete_data = tileUpdate.substr(len("delete,")).split(':')
delete_entity(delete_data[1],delete_data[0])
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)
@ -43,7 +45,21 @@ 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)
func delete_entity(entity_id : String, type : String):
var entity : Node2D = get_node_or_null( str(type + "-" + entity_id))
if entity:
entity.queue_free()

View File

@ -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()

View File

@ -37,6 +37,8 @@ Resources:
NlbTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
HealthCheckProtocol: TCP
HealthCheckPort: 80
Port: 7777
Protocol: TCP_UDP
TargetGroupAttributes:

View File

@ -6,13 +6,23 @@ Parameters:
Description: The AWS CloudWatch log group to output logs to.
Default: "/ecs/dt"
environment:
Type: String
Description: Name of the environment to use in naming.
Default: production
DockerTag:
Description: Tag in DockerHub to deploy
Type: String
Default: "latest"
Resources:
LogGroup:
Type: AWS::Logs::LogGroup
Properties:
RetentionInDays: 7
LogGroupName: !Ref LogGroupName
LogGroupName: !Sub "${LogGroupName}/${environment}"
TaskDefinition:
Type: AWS::ECS::TaskDefinition
@ -20,11 +30,15 @@ Resources:
ContainerDefinitions:
- Name: defend-together
Essential: 'true'
Image: "josephbmanley/defend-together:latest"
MemoryReservation: 800
Image: !Sub "josephbmanley/defend-together:${DockerTag}"
MemoryReservation: 250
PortMappings:
- HostPort: 0
- HostPort: 7777
ContainerPort: 7777
Protocol: udp
- HostPort: 80
ContainerPort: 80
Protocol: tcp
LogConfiguration:
LogDriver: awslogs
Options:

View File

@ -12,11 +12,15 @@ Parameters:
Type: String
Description: Name of the release name of the stack version to use.
Default: production
AllowedValues: ['develop', 'production']
AllowedValues: ['develop', 'stage', 'production']
ConstraintDescription: "Must be a possible release version."
VpcId:
Description: ID of the VPC
Type: AWS::EC2::VPC::Id
DockerTag:
Description: Tag in DockerHub to deploy
Type: String
Default: "latest"
#-----------------
# Load Balancing
@ -95,7 +99,9 @@ Resources:
Properties:
TemplateURL: !Sub 'https://s3.${AWS::Region}.amazonaws.com/sumu-stacks/dt/${release}/cloudformation/dt/task.yaml'
Parameters:
environment: !Ref environment
LogGroupName: !Ref LogGroup
DockerTag: !Ref DockerTag
EcsService:

View File

@ -1,9 +1,15 @@
FROM fedora:32
RUN /bin/bash -c "dnf install g++ enet-devel gtest gmock gmock-devel gtest-devel -y"
# Build and setup app
RUN dnf install g++ enet-devel gtest gmock gmock-devel gtest-devel nginx -y
ADD / /dt
RUN cd /dt; /dt/build.sh
CMD ["/dt/builds/server.out"]
RUN echo "Container is healthy!" > /usr/share/nginx/html/index.html
CMD /usr/sbin/nginx ; /dt/builds/server.out
EXPOSE 80
EXPOSE 7777/udp

View File

@ -7,4 +7,11 @@ if [ -f builds/server.out ]; then
rm builds/server.out
fi
g++ main.cpp -o builds/server.out -lenet -lpthread
g++ main.cpp -o builds/server.out -lenet -lpthread
if [ $? = 0 ]
then
echo "Build successfully!"
else
exit -1
fi

View File

@ -1,13 +1,11 @@
#ifndef CONSOLE_HPP
#define CONSOLE_HPP
#include <stdio.h>
#include <pthread.h>
#include <iostream>
#include <string>
#include <cstring>
bool console_is_running = false;
#include "gameserver.hpp"
class ServerConsole
{
@ -22,19 +20,22 @@ class ServerConsole
void * console_logic(void *)
{
printf("Started server console...\n");
std::cout << "Started server console..." << std::endl;
while(console_is_running)
std::string input_string;
while(game_is_running)
{
char c [256];
fgets(c,sizeof(c), stdin);
if(c[0] == 'q')
if(std::getline(std::cin, input_string))
{
console_is_running = false;
}
else
{
printf("Invalid console command!\n");
if(input_string == "stop")
{
game_is_running = false;
}
else
{
std::cout << "Invalid console command!" << std::endl;
}
}
}
return 0;
@ -43,7 +44,7 @@ void * console_logic(void *)
ServerConsole::ServerConsole(void)
{
//Intialized values
console_is_running = true;
game_is_running = true;
thread = pthread_t();
@ -61,7 +62,7 @@ void ServerConsole::stop(void)
bool ServerConsole::is_running(void)
{
return console_is_running;
return game_is_running;
}
#endif

View File

@ -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();

View File

@ -4,6 +4,7 @@
#include <string>
#include <iostream>
#include <vector>
#include <sstream>
#include "gameentity.hpp"
@ -15,10 +16,18 @@ 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;
int size_x;
@ -53,30 +62,78 @@ 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)
{
if(map_data[entities[i].get_x() + x][entities[i].get_y() + y] % 2 == 1)
{
entities[i].set_x(entities[i].get_x() + x);
entities[i].set_y(entities[i].get_y() + y);
}
return entities[i].get_dump();
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++)
{
if(entities[i].get_id() == entity_id && entities[i].get_type() == entity_type)
{
GameEntity* entity = &entities[i];
//if(map_data[entities[i].get_x() + x][entities[i].get_y() + y] % 2 == 1)
//{
if(entity->get_x() + x < 0)
{
entity->set_x(0);
}
else if(entity->get_x() + x >= this->get_size_x())
{
entity->set_x(this->get_size_x() - 1);
}
else
{
entity->set_x(entities[i].get_x() + x);
}
if(entity->get_y() + y < 0)
{
entity->set_y(0);
}
else if(entity->get_y() + y >= this->get_size_y())
{
entity->set_y(this->get_size_y() - 1);
}
else
{
entity->set_y(entities[i].get_y() + y);
}
return entity->get_dump();
}
}
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)
@ -89,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))
@ -137,4 +211,83 @@ int GameMap::get_size_y(void)
return size_y;
}
bool GameMap::entity_exists(std::string entity_id, std::string entity_type)
{
for(int i = 0; i < entities.size(); i++)
{
GameEntity* entity = &entities[i];
if(entity->get_id() == entity_id)
{
if(entity->get_type() == entity_type)
{
return true;
}
}
}
return false;
}
bool erase(std::vector<GameEntity> &v, GameEntity key)
{
for(auto it = v.begin(); it != v.end();)
{
if (it->get_id() == key.get_id())
{
it = v.erase(it);
return true;
}
else
{
++it;
}
}
return false;
}
bool GameMap::remove_entity(std::string entity_id, std::string entity_type)
{
for(int i = 0; i < entities.size(); i++)
{
if(entities[i].get_id() == entity_id && entities[i].get_type() == entity_type)
{
return erase(entities, entities[i]);
}
}
return false;
}
int GameMap::get_entity_pos_x(std::string entity_id, std::string entity_type)
{
for(int i = 0; i < entities.size(); i++)
{
GameEntity* entity = &entities[i];
if(entity->get_id() == entity_id)
{
if(entity->get_type() == entity_type)
{
return entity->get_x();
}
}
}
return -1;
}
int GameMap::get_entity_pos_y(std::string entity_id, std::string entity_type)
{
for(int i = 0; i < entities.size(); i++)
{
GameEntity* entity = &entities[i];
if(entity->get_id() == entity_id)
{
if(entity->get_type() == entity_type)
{
return entity->get_y();
}
}
}
return -1;
}
#endif

View File

@ -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,175 @@ 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 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;
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);
}
}
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);
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);
}
}

View File

@ -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 = 1;
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,44 +60,26 @@ 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;
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;
}
//Destroy packet now that it has been used
enet_packet_destroy (event.packet);
//Channel #0 processes authentication, world updates,
// and player movement
if (event.channelID == 0)
{
gameserver::ProcessGeneralInput(&event);
}
//Channel #1 proccesses chat messages
else if(event.channelID == 1)
{
gameserver::ProcessChatMessage(&event);
}
enet_packet_destroy (event.packet);
break;
case ENET_EVENT_TYPE_DISCONNECT:
std::cout << event.peer -> data << " disconnected." << std::endl;
//Remove peer data on disconnect
// DELETE ENTITY HERE
usernames[event.peer -> incomingPeerID] = "";
gameserver::HandlePlayerDisconnect(&event);
//Open peer for new connection
event.peer -> data = NULL;
}
}

10
server/nginx.conf Normal file
View File

@ -0,0 +1,10 @@
server {
location / {
root /usr/share/nginx/html;
}
location /health {
return 200 'alive';
add_header Content-Type text/plain;
}
}

View File

@ -1,4 +1,9 @@
#!/bin/sh
mkdir -p builds
g++ tests.cpp -o builds/tests.out -lgtest
./builds/tests.out
if [ $? = 0 ]
then
./builds/tests.out
else
exit -1
fi

View File

@ -1,6 +1,7 @@
#include <gtest/gtest.h>
#include <string>
#include <iostream>
#include "gameentity.hpp"
#include "gamemap.hpp"
@ -38,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
@ -61,6 +62,87 @@ TEST(GameMapTest, CheckMapSize)
EXPECT_EQ(map.get_size_x(), 16);
EXPECT_EQ(map.get_size_y(), 4);
}
TEST(GameMapTest, CreateEntity)
{
GameMap map = CreateMapEntity();
map.spawn_entity(TEST_ENTITY_ID, TEST_ENTITY_TYPE);
EXPECT_TRUE(map.entity_exists(TEST_ENTITY_ID, TEST_ENTITY_TYPE));
}
TEST(GameMapTest, IntialEntityPos)
{
GameMap map = CreateMapEntity();
map.spawn_entity(TEST_ENTITY_ID, TEST_ENTITY_TYPE, 0,0);
EXPECT_EQ(map.get_entity_pos_x(TEST_ENTITY_ID, TEST_ENTITY_TYPE), 0);
EXPECT_EQ(map.get_entity_pos_y(TEST_ENTITY_ID, TEST_ENTITY_TYPE), 0);
}
TEST(GameMapTest, MoveEntityFromZero)
{
GameMap map = CreateMapEntity();
map.spawn_entity(TEST_ENTITY_ID, TEST_ENTITY_TYPE, 0,0);
//Move entity
map.move_entity(TEST_ENTITY_ID, TEST_ENTITY_TYPE, 2, 1);
EXPECT_EQ(map.get_entity_pos_x(TEST_ENTITY_ID, TEST_ENTITY_TYPE), 2);
EXPECT_EQ(map.get_entity_pos_y(TEST_ENTITY_ID, TEST_ENTITY_TYPE), 1);
}
TEST(GameMapTest, MoveEntityRelative)
{
GameMap map = CreateMapEntity();
map.spawn_entity(TEST_ENTITY_ID, TEST_ENTITY_TYPE, 0,0);
//Move entity
map.move_entity(TEST_ENTITY_ID, TEST_ENTITY_TYPE, 2, 1);
//Move entity again!
map.move_entity(TEST_ENTITY_ID, TEST_ENTITY_TYPE, 1, 1);
EXPECT_EQ(map.get_entity_pos_x(TEST_ENTITY_ID, TEST_ENTITY_TYPE), 3);
EXPECT_EQ(map.get_entity_pos_y(TEST_ENTITY_ID, TEST_ENTITY_TYPE), 2);
}
TEST(GameMapTest, MoveEntityNegativeFromZero)
{
GameMap map = CreateMapEntity();
map.spawn_entity(TEST_ENTITY_ID, TEST_ENTITY_TYPE, 0, 0);
//Move entity
map.move_entity(TEST_ENTITY_ID, TEST_ENTITY_TYPE, -1, -1);
EXPECT_EQ(map.get_entity_pos_x(TEST_ENTITY_ID, TEST_ENTITY_TYPE), 0);
EXPECT_EQ(map.get_entity_pos_y(TEST_ENTITY_ID, TEST_ENTITY_TYPE), 0);
}
TEST(GameMapTest, MoveEntityNegativeFromNonZero)
{
GameMap map = CreateMapEntity();
map.spawn_entity(TEST_ENTITY_ID, TEST_ENTITY_TYPE, 1, 1);
//Move entity
std::cout << std::string(map.move_entity(TEST_ENTITY_ID, TEST_ENTITY_TYPE, -2, -2)) << std::endl;
EXPECT_EQ(map.get_entity_pos_x(TEST_ENTITY_ID, TEST_ENTITY_TYPE), 0);
EXPECT_EQ(map.get_entity_pos_y(TEST_ENTITY_ID, TEST_ENTITY_TYPE), 0);
}
TEST(GameMapTest, StopEntityFromMovingOutOfBounds)
{
GameMap map = CreateMapEntity();
map.spawn_entity(TEST_ENTITY_ID, TEST_ENTITY_TYPE, map.get_size_x()-1, map.get_size_y()-1);
//Move entity
map.move_entity(TEST_ENTITY_ID, TEST_ENTITY_TYPE, 2, 2);
EXPECT_EQ(map.get_entity_pos_x(TEST_ENTITY_ID, TEST_ENTITY_TYPE), map.get_size_x() - 1);
EXPECT_EQ(map.get_entity_pos_y(TEST_ENTITY_ID, TEST_ENTITY_TYPE), map.get_size_y() - 1);
}
TEST(GameMapTest, RemoveMapEntity)
{
GameMap map = CreateMapEntity();
map.spawn_entity(TEST_ENTITY_ID, TEST_ENTITY_TYPE);
EXPECT_TRUE(map.remove_entity(TEST_ENTITY_ID, TEST_ENTITY_TYPE));
EXPECT_FALSE(map.entity_exists(TEST_ENTITY_ID, TEST_ENTITY_TYPE));
}
int main(int argc, char* argv[])
{