diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..ffa3938
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,5 @@
+# These are supported funding model platforms
+
+github: hewiefreeman
+patreon: hewiefreeman
+custom: paypal.me/hewiefreeman
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
new file mode 100644
index 0000000..91b29ba
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,71 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+#
+# ******** NOTE ********
+# We have attempted to detect the languages in your repository. Please check
+# the `language` matrix defined below to confirm you have the correct set of
+# supported CodeQL languages.
+#
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ master ]
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches: [ master ]
+ schedule:
+ - cron: '17 4 * * 3'
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ 'go' ]
+ # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
+ # Learn more:
+ # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v2
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v1
+ with:
+ languages: ${{ matrix.language }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+ # queries: ./path/to/local/query, your-org/your-repo/queries@main
+
+ # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
+ # If this step fails, then you should remove it and run the build manually (see below)
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v1
+
+ # âšī¸ Command-line programs to run using the OS shell.
+ # đ https://git.io/JvXDl
+
+ # âī¸ If the Autobuild fails above, remove it and uncomment the following three lines
+ # and modify them (or add more) to build your code if your project
+ # uses a compiled language
+
+ #- run: |
+ # make bootstrap
+ # make release
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v1
diff --git a/CHANGE_LOG.md b/CHANGE_LOG.md
new file mode 100644
index 0000000..6610fda
--- /dev/null
+++ b/CHANGE_LOG.md
@@ -0,0 +1,22 @@
+# Change Log
+This is where all important changes and bug fixes will be described in detail. Each entry is labeled with a patch version number, and that is the version in which the changes and/or fixes were made. All prior versions will lack the described changes and/or fixes described.
+
+> If you are experiencing any bugs or errors because of an update, please report them by [opening an issue](https://github.com/hewiefreeman/GopherGameServer/issues/new/choose)!
+
+### Key
+ :wrench: : Bug fix
+
+ :warning: : Code-breaking change
+
+ :newspaper: : New feature
+
+ :monorail: : Optimization
+
+## v1.0-BETA.2
+ - :newspaper: Added a `version` macro to display current running server version
+ - :monorail: :warning: ([commit](https://github.com/hewiefreeman/GopherGameServer/commit/941c558bfe44f237f150918187785cceb8aafecd)) Restoring logic has been simplified, but any previous version restore files will fail to restore!
+
+## v1.0-ALPHA.5
+ - :monorail: :warning: ([commit](https://github.com/hewiefreeman/GopherGameServer/commit/a5edd57bcc61fc6f5d10b194f4a433e7a5ed51da)) Merged the `rooms` and `users` packages into one `core` package. **Requires refactoring your server code)**
+
+> Version **1.0-ALPHA.5** merged the packages `users` and `rooms` into a single package `core`. If you've updated your server from **1.0-ALPHA.4** or below, you will need to edit your code and replace any instance of `rooms` or `users` with `core`. This also changes how [Rooms](https://github.com/hewiefreeman/GopherGameServer/wiki/Rooms#blue_book-creating--deleting-rooms) are **created** and **retrieved** ([core.GetRoom](https://godoc.org/github.com/hewiefreeman/GopherGameServer/core#GetRoom)), and how [Users](https://godoc.org/github.com/hewiefreeman/GopherGameServer/core#GetUser) are **retrieved**.
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index b1e3e68..7616198 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -55,7 +55,7 @@ further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
-reported by contacting the project team at dominiquedebergue@gmail.com. All
+reported by contacting the project team at tbd@gmail.com . All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..0611485
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,14 @@
+## Ways to contribute
+
+1) See if there is an Issue that you can take up to fix or implement
+2) Help expand and clarify documentation and/or usage section
+3) Have a good idea? Open and submit a detailed Issue
+
+## Guidlines
+
+1) First, check to see if a similar issue has already been opened
+2) Follow the [Code of Conduct](https://github.com/hewiefreeman/GopherGameServer/blob/master/CODE_OF_CONDUCT.md)
+3) BE DETAILED in your Pull Requests
+4) Do not introduce any further 3rd-party package dependencies unless provided a valid reason
+5) Do not submit content that does not pass golint, or has not been formatted with `gofmt`
+6) Do not submit content that has little to no documentation on exported functions, variables, etc
diff --git a/LICENSE b/LICENSE
index c05e850..d9a10c0 100644
--- a/LICENSE
+++ b/LICENSE
@@ -174,28 +174,3 @@
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright [2018] [Dominique Claude Debergue]
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
diff --git a/README.md b/README.md
index d7817cc..451c4f7 100644
--- a/README.md
+++ b/README.md
@@ -1,37 +1,45 @@
-# Gopher Game Server
+
+
-[](https://opensource.org/licenses/Apache-2.0) [](https://godoc.org/github.com/hewiefreeman/GopherGameServer) [](https://goreportcard.com/report/github.com/hewiefreeman/GopherGameServer)
+
-Gopher Game Server is a full featured, super fast, and fully customizable game server written in Go. Comes with a client API for JavaScript (and eventually Java, C++, and C).
+Gopher Game Server provides a flexible and diverse set of tools that greatly ease developments of any type of online multiplayer game, or real-time application. GGS does all the heavy lifting for you, ensuring you never need to worry about synchronizing or data type conversions.
--**PROJECT IN DEVELOPMENT**-
+Moreover, Gopher has a built-in, fully customizable SQL client authentication mechanism that creates and manages users' accounts for you. It even ties in a friending tool, so users can befriend and invite one another to groups, check each other's status, and more. All components are easily configurable and customizable for any specific project's needs.
-Gopher Game Server is designed to provide all necessary tools to ease developments of any type of online game (or any real-time app/chat). Gopher will handle all server-side synchronizing and data type conversions, therefore, client actions receiving, variable setting, message sending, and other functionalities are unproblematic.
+### :star: Main features
-Gopher uses WebSockets and JSON to pass messages between the clients and the server. The JSON enabled server is designed to pass any types of data between client and server without cares of type conversions on either end. WebSockets increases efficiency of the server as far as possible on the network, since the WebSocket protocol is newer and doesn't send as much header and meta data that standard HTTP and most other protocols require.
+ - Super easy APIs for server, database, and client coding
+ - Chat, private messaging, and voice chat
+ - Customizable client authentication (\***1**)
+ - Built-in friending mechanism (\***1**)
+ - Supports multiple connections on the same User
+ - Server saves state on shut-down and restores on reboot (\***2**)
-Moreover, Gopher has a built-in, fully customizable MySQL client authentication mechanism that manages users' accounts for you. It even ties in a friending tool, so users can befriend and invite one another to groups, check each other's status, and more. All components are easily configurable and customizable for specific project's needs.
+> (\***1**) A MySQL (or similar SQL) database is required for the authentication/friending feature, but is an optional (like most) feature that can be enabled or disabled to use your own implementations.
-### Main features:
+> (\***2**) When updating and restarting your server, you might need to be able to recover any rooms that were in the middle of a game. This enables you to do so with minimal effort.
- - Super easy and flexible APIs for server, database, and client coding
- - Chat, private messaging, and voice chat
- - Customizable client authentication (**\***)
- - Built-in Friending (**\***)
- - Supports multiple connections on the same User
- - Server saves state on shut-down and restores on reboot
- - Tools provided for administrating server while running
+### Upcoming features
-(**\***) A MySQL (or similar SQL) database is required for the authentication/friending feature, but is an optional (like most) feature that can be enabled or disabled to use your own implementations.
+ - Distributed load balancer and server coordinator
+ - Distributed server broadcasts
+ - GUI for administrating and monitoring servers
+ - Integration with [GopherDB](https://github.com/hewiefreeman/GopherDB) when stable (\***1**)
-# Client APIs
+> (\***1**) MySQL isn't very scalable on it's own, and the SQL implementation for storing friend info is probably not the most efficient. Hence, it is recommended to put the friends table into a separate database cluster. GopherDB, on the other hand, is a very promising database project that will greatly increase server efficiency, and could possibly even outperform MySQL overall. It has a built-in authentication table type, which takes a substantial load off the game servers, and further secures your users' private information. It also supports nested values which are deep-validated through table schemas, so you can store complex information using a wide variety of data types and rules. You can follow the project and get more info with the link above!
- - JavaScript: [Gopher Client JS](https://github.com/hewiefreeman/GopherClientJS)
+### Change Log
+[CHANGE_LOG.md](https://github.com/hewiefreeman/GopherGameServer/blob/master/CHANGE_LOG.md)
+
+# :video_game: Client APIs
-The Java, C++, and C (possibly more with some help) client APIs will be made after completing version 1.0 and the JavaScript client API.
+ - JavaScript: [GopherClientJS](https://github.com/hewiefreeman/GopherClientJS)
+
+ > If you want to make a client API in an unsupported language and want to know where to start and/or have any questions, feel free to open a new issue!
-# Installing
-Gopher Game Server requires at least **Go v1.8+**, and **MySQL v5.7+** for the authentication and friending features.
+# :file_folder: Installing
+Gopher Game Server requires at least **Go v1.8+** (and **MySQL v5.7+** for the authentication and friending features).
First, install the dependencies:
@@ -43,22 +51,69 @@ Then install the server:
go get github.com/hewiefreeman/GopherGameServer
-# Documentation
+# :books: Usage
+
+[:bookmark: Wiki Home](https://github.com/hewiefreeman/GopherGameServer/wiki)
+
+### Table of Contents
+
+1) [**Getting Started**](https://github.com/hewiefreeman/GopherGameServer/wiki/Getting-Started)
+ - [Set-Up](https://github.com/hewiefreeman/GopherGameServer/wiki/Getting-Started#blue_book-set-up)
+ - [Core Server Settings](https://github.com/hewiefreeman/GopherGameServer/wiki/Getting-Started#blue_book-core-server-settings)
+ - [Server Callbacks](https://github.com/hewiefreeman/GopherGameServer/wiki/Getting-Started#blue_book-server-callbacks)
+ - [Macro Commands](https://github.com/hewiefreeman/GopherGameServer/wiki/Getting-Started#blue_book-macro-commands)
+2) [**Rooms**](https://github.com/hewiefreeman/GopherGameServer/wiki/Rooms)
+ - [Room Types](https://github.com/hewiefreeman/GopherGameServer/wiki/Rooms#blue_book-room-types)
+ - [Room Broadcasts](https://github.com/hewiefreeman/GopherGameServer/wiki/Rooms#blue_book-room-broadcasts)
+ - [Room Callbacks](https://github.com/hewiefreeman/GopherGameServer/wiki/Rooms#blue_book-room-callbacks)
+ - [Creating & Deleting Rooms](https://github.com/hewiefreeman/GopherGameServer/wiki/Rooms#blue_book-creating--deleting-rooms)
+ - [Room Variables](https://github.com/hewiefreeman/GopherGameServer/wiki/Rooms#blue_book-room-variables)
+ - [Messaging](https://github.com/hewiefreeman/GopherGameServer/wiki/Rooms#blue_book-messaging)
+3) [**Users**](https://github.com/hewiefreeman/GopherGameServer/wiki/Users)
+ - [Login & Logout](https://github.com/hewiefreeman/GopherGameServer/wiki/Users#blue_book-login-and-logout)
+ - [Joining & Leaving Rooms](https://github.com/hewiefreeman/GopherGameServer/wiki/Users#blue_book-joining--leaving-rooms)
+ - [User Variables](https://github.com/hewiefreeman/GopherGameServer/wiki/Users#blue_book-user-variables)
+ - [Initiating and Revoking Room Invites](https://github.com/hewiefreeman/GopherGameServer/wiki/Users#blue_book-initiating-and-revoking-room-invites)
+ - [User Status](https://github.com/hewiefreeman/GopherGameServer/wiki/Users#blue_book-user-status)
+ - [Messaging](https://github.com/hewiefreeman/GopherGameServer/wiki/Users#blue_book-messaging)
+4) [**Custom Client Actions**](https://github.com/hewiefreeman/GopherGameServer/wiki/Custom-Client-Actions)
+ - [Creating a Custom Client Action](https://github.com/hewiefreeman/GopherGameServer/wiki/Custom-Client-Actions#blue_book-creating-a-custom-client-action)
+ - [Responding to a Custom Client Action](https://github.com/hewiefreeman/GopherGameServer/wiki/Custom-Client-Actions#blue_book-responding-to-a-custom-client-action)
+6) [**Saving & Restoring**](https://github.com/hewiefreeman/GopherGameServer/wiki/Saving-&-Restoring)
+ - [Set-Up](https://github.com/hewiefreeman/GopherGameServer/wiki/Saving-&-Restoring#blue_book-set-up)
+5) [**SQL Features**](https://github.com/hewiefreeman/GopherGameServer/wiki/SQL-Features)
+ - [Set-Up](https://github.com/hewiefreeman/GopherGameServer/wiki/SQL-Features#blue_book-set-up)
+ - [Authenticating Clients](https://github.com/hewiefreeman/GopherGameServer/wiki/SQL-Features#blue_book-authenticating-clients)
+ - [Custom Account Info](https://github.com/hewiefreeman/GopherGameServer/wiki/SQL-Features#blue_book-custom-account-info)
+ - [Customizing Authentication Features](https://github.com/hewiefreeman/GopherGameServer/wiki/SQL-Features#blue_book-customizing-authentication-features)
+ - [Auto-Login (Remember Me)](https://github.com/hewiefreeman/GopherGameServer/wiki/SQL-Features#blue_book-auto-login-remember-me)
+ - [Friending](https://github.com/hewiefreeman/GopherGameServer/wiki/SQL-Features#blue_book-friending)
+
+# :scroll: Documentation
[Package gopher](https://godoc.org/github.com/hewiefreeman/GopherGameServer) - Main server package for startup and settings
-[Package rooms](https://godoc.org/github.com/hewiefreeman/GopherGameServer/rooms) - Package for using the Room, RoomType, and RoomUser types
-
-[Package users](https://godoc.org/github.com/hewiefreeman/GopherGameServer/users) - Package for using the User type
+[Package core](https://godoc.org/github.com/hewiefreeman/GopherGameServer/core) - Package for all User and Room functionality
[Package actions](https://godoc.org/github.com/hewiefreeman/GopherGameServer/actions) - Package for making custom client actions
[Package database](https://godoc.org/github.com/hewiefreeman/GopherGameServer/database) - Package for customizing your database
-# Usage
+# :milky_way: Contributions
+Contributions are open and welcomed! Help is needed for everything from documentation, cleaning up code, performance enhancements, client APIs and more. Don't forget to show your support with a :star:!
+
+If you want to make a client API in an unsupported language and want to know where to start and/or have any questions, feel free to open a new issue!
+
+Please read the following articles before submitting any contributions or filing an Issue:
+
+ - [Contribution Guidlines](https://github.com/hewiefreeman/GopherGameServer/blob/master/CONTRIBUTING.md)
+ - [Code of Conduct](https://github.com/hewiefreeman/GopherGameServer/blob/master/CODE_OF_CONDUCT.md)
+
+
-(Coming soon...)
+
GopherGameServer and all of it's contents Copyright 2022 Dominique Debergue
+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at:
-# Contributions
+ `http://www.apache.org/licenses/LICENSE-2.0`
-**Gopher Game Server will be open for contributions as soon as version 1.0 is finished. At that time, contribution information will be posted here.**
+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
diff --git a/actions/actions.go b/actions/actions.go
index 9465c68..ab80bd0 100644
--- a/actions/actions.go
+++ b/actions/actions.go
@@ -4,8 +4,8 @@ package actions
import (
"errors"
"github.com/gorilla/websocket"
+ "github.com/hewiefreeman/GopherGameServer/core"
"github.com/hewiefreeman/GopherGameServer/helpers"
- "github.com/hewiefreeman/GopherGameServer/users"
)
// CustomClientAction is an action that you can handle on the server from
@@ -16,7 +16,7 @@ import (
type CustomClientAction struct {
dataType int
- callback func(interface{}, Client)
+ callback func(interface{}, *Client)
}
// Client objects are created and sent along with your CustomClientAction callback function when a
@@ -24,7 +24,7 @@ type CustomClientAction struct {
type Client struct {
action string
- user *users.User
+ user *core.User
connID string
socket *websocket.Conn
@@ -47,8 +47,8 @@ var (
// Default `ClientError`s
const (
- ErrorMismatchedTypes = iota + 1001 // Client didn't pass the right data type for the given action
- ErrorUnrecognizedAction // The custom action has not been defined
+ ErrorMismatchedTypes = iota + 1001 // Client didn't pass the right data type for the given action
+ ErrorUnrecognizedAction // The custom action has not been defined
)
// These are the accepted data types that a client can send with a CustomClientMessage. You must use one
@@ -75,7 +75,7 @@ const (
//
// (*)Callback function format:
//
-// func yourFunction(actionData interface{}, client Client) {
+// func yourFunction(actionData interface{}, client *Client) {
// //...
//
// // optional client response
@@ -88,13 +88,14 @@ const (
//
//
// Note: This function can only be called BEFORE starting the server.
-func New(actionType string, dataType int, callback func(interface{}, Client)) error {
+func New(actionType string, dataType int, callback func(interface{}, *Client)) error {
if serverStarted {
return errors.New("Cannot make a new CustomClientAction once the server has started")
}
customClientActions[actionType] = CustomClientAction{
dataType: dataType,
- callback: callback}
+ callback: callback,
+ }
return nil
}
@@ -116,7 +117,7 @@ func NoError() ClientError {
//
// WARNING: This is only meant for internal Gopher Game Server mechanics. Your CustomClientAction callbacks are called
// from this function. This could spawn errors and/or memory leaks.
-func HandleCustomClientAction(action string, data interface{}, user *users.User, conn *websocket.Conn, connID string) {
+func HandleCustomClientAction(action string, data interface{}, user *core.User, conn *websocket.Conn, connID string) {
client := Client{user: user, action: action, socket: conn, connID: connID, responded: false}
// CHECK IF ACTION EXISTS
if customAction, ok := customClientActions[action]; ok {
@@ -126,7 +127,7 @@ func HandleCustomClientAction(action string, data interface{}, user *users.User,
return
}
//EXECUTE CALLBACK
- customAction.callback(data, client)
+ customAction.callback(data, &client)
} else {
client.Respond(nil, NewError("Unrecognized action", ErrorUnrecognizedAction))
}
@@ -198,8 +199,8 @@ func typesMatch(data interface{}, theType int) bool {
// is needed.
//
// NOTE: A response can only be sent once to a Client. Any more calls to Respond() on the same Client will not send a response,
-// nor do anything at all. If you want to send a stream of messages to the Client, first get their User object with users.Get() using
-// the Client's Name(). Then you can send Data messages directly to the User with the *User.DataMessage() function.
+// nor do anything at all. If you want to send a stream of messages to the Client, first get their User object with *Client.User(),
+// then you can send data messages directly to the User with the *User.DataMessage() function.
func (c *Client) Respond(response interface{}, err ClientError) {
//YOU CAN ONLY RESPOND ONCE
if (*c).responded {
@@ -207,17 +208,19 @@ func (c *Client) Respond(response interface{}, err ClientError) {
}
(*c).responded = true
//CONSTRUCT MESSAGE
- r := make(map[string]interface{})
- r[helpers.ServerActionCustomClientActionResponse] = make(map[string]interface{})
- r[helpers.ServerActionCustomClientActionResponse].(map[string]interface{})["a"] = (*c).action
+ r := map[string]map[string]interface{}{
+ helpers.ServerActionCustomClientActionResponse: {
+ "a": (*c).action,
+ },
+ }
if err.id != -1 {
- r[helpers.ServerActionCustomClientActionResponse].(map[string]interface{})["e"] = make(map[string]interface{})
- r[helpers.ServerActionCustomClientActionResponse].(map[string]interface{})["e"].(map[string]interface{})["m"] = err.message
- r[helpers.ServerActionCustomClientActionResponse].(map[string]interface{})["e"].(map[string]interface{})["id"] = err.id
+ r[helpers.ServerActionCustomClientActionResponse]["e"] = map[string]interface{}{
+ "m": err.message,
+ "id": err.id,
+ }
} else {
- r[helpers.ServerActionCustomClientActionResponse].(map[string]interface{})["r"] = response
+ r[helpers.ServerActionCustomClientActionResponse]["r"] = response
}
-
//SEND MESSAGE TO CLIENT
(*c).socket.WriteJSON(r)
}
@@ -227,7 +230,7 @@ func (c *Client) Respond(response interface{}, err ClientError) {
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// User gets the *User of the Client.
-func (c *Client) User() *users.User {
+func (c *Client) User() *core.User {
return c.user
}
@@ -262,14 +265,12 @@ func SetServerStarted(val bool) {
func Pause() {
if !serverPaused {
serverPaused = true
- serverStarted = false
}
}
// Resume is only for internal Gopher Game Server mechanics.
func Resume() {
if serverPaused {
- serverStarted = true
serverPaused = false
}
}
diff --git a/callbacks.go b/callbacks.go
index 2b140c0..16d1a83 100644
--- a/callbacks.go
+++ b/callbacks.go
@@ -2,14 +2,17 @@ package gopher
import (
"errors"
+ "github.com/hewiefreeman/GopherGameServer/core"
"github.com/hewiefreeman/GopherGameServer/database"
- "github.com/hewiefreeman/GopherGameServer/users"
"net/http"
)
const (
+ // ErrorIncorrectFunction is thrown when function input or return parameters don't match with the callback
ErrorIncorrectFunction = "Incorrect function parameters or return parameters"
- ErrorServerRunning = "Cannot call when the server is running."
+ // ErrorServerRunning is thrown when an action cannot be taken because the server is running. Pausing the server
+ // will enable you to run the command.
+ ErrorServerRunning = "Cannot call when the server is running."
)
// SetStartCallback sets the callback that triggers when the server first starts up. The
@@ -63,13 +66,13 @@ func SetResumeCallback(cb interface{}) error {
return nil
}
-// SetStopCallback sets the callback that triggers when the server is stopped. The
+// SetShutDownCallback sets the callback that triggers when the server is shut down. The
// function passed must have the same parameter types as the following example:
//
// func serverStopped(){
// //code...
// }
-func SetStopCallback(cb interface{}) error {
+func SetShutDownCallback(cb interface{}) error {
if serverStarted {
return errors.New(ErrorServerRunning)
} else if callback, ok := cb.(func()); ok {
@@ -88,16 +91,15 @@ func SetStopCallback(cb interface{}) error {
// }
//
// The function returns a boolean. If false is returned, the client will receive an HTTP error `http.StatusForbidden` and
-// will be rejected from the server. This can be used to, for instance, make a black/white list for your server.
+// will be rejected from the server. This can be used to, for instance, make a black/white list or implement client sessions.
func SetClientConnectCallback(cb interface{}) error {
if serverStarted {
return errors.New(ErrorServerRunning)
} else if callback, ok := cb.(func(*http.ResponseWriter, *http.Request) bool); ok {
clientConnectCallback = callback
- } else {
- return errors.New(ErrorIncorrectFunction)
+ return nil
}
- return nil
+ return errors.New(ErrorIncorrectFunction)
}
// SetLoginCallback sets the callback that triggers when a client logs in as a User. The
@@ -123,12 +125,11 @@ func SetLoginCallback(cb interface{}) error {
if (*settings).EnableSqlFeatures {
database.LoginCallback = callback
} else {
- users.LoginCallback = callback
+ core.LoginCallback = callback
}
- } else {
- return errors.New(ErrorIncorrectFunction)
+ return nil
}
- return nil
+ return errors.New(ErrorIncorrectFunction)
}
// SetLogoutCallback sets the callback that triggers when a client logs out from a User. The
@@ -143,11 +144,10 @@ func SetLogoutCallback(cb interface{}) error {
if serverStarted {
return errors.New(ErrorServerRunning)
} else if callback, ok := cb.(func(string, int)); ok {
- users.LogoutCallback = callback
- } else {
- return errors.New(ErrorIncorrectFunction)
+ core.LogoutCallback = callback
+ return nil
}
- return nil
+ return errors.New(ErrorIncorrectFunction)
}
// SetSignupCallback sets the callback that triggers when a client makes an account. The
@@ -167,10 +167,9 @@ func SetSignupCallback(cb interface{}) error {
return errors.New(ErrorServerRunning)
} else if callback, ok := cb.(func(string, map[string]interface{}) bool); ok {
database.SignUpCallback = callback
- } else {
- return errors.New(ErrorIncorrectFunction)
+ return nil
}
- return nil
+ return errors.New(ErrorIncorrectFunction)
}
// SetDeleteAccountCallback sets the callback that triggers when a client deletes their account. The
@@ -194,10 +193,9 @@ func SetDeleteAccountCallback(cb interface{}) error {
return errors.New(ErrorServerRunning)
} else if callback, ok := cb.(func(string, int, map[string]interface{}, map[string]interface{}) bool); ok {
database.DeleteAccountCallback = callback
- } else {
- return errors.New(ErrorIncorrectFunction)
+ return nil
}
- return nil
+ return errors.New(ErrorIncorrectFunction)
}
// SetAccountInfoChangeCallback sets the callback that triggers when a client changes an `AccountInfoColumn`. The
@@ -221,10 +219,9 @@ func SetAccountInfoChangeCallback(cb interface{}) error {
return errors.New(ErrorServerRunning)
} else if callback, ok := cb.(func(string, int, map[string]interface{}, map[string]interface{}) bool); ok {
database.AccountInfoChangeCallback = callback
- } else {
- return errors.New(ErrorIncorrectFunction)
+ return nil
}
- return nil
+ return errors.New(ErrorIncorrectFunction)
}
// SetPasswordChangeCallback sets the callback that triggers when a client changes their password. The
@@ -248,8 +245,7 @@ func SetPasswordChangeCallback(cb interface{}) error {
return errors.New(ErrorServerRunning)
} else if callback, ok := cb.(func(string, int, map[string]interface{}, map[string]interface{}) bool); ok {
database.PasswordChangeCallback = callback
- } else {
- return errors.New(ErrorIncorrectFunction)
+ return nil
}
- return nil
+ return errors.New(ErrorIncorrectFunction)
}
diff --git a/clientActions.go b/clientActions.go
index 3cf575d..242d434 100644
--- a/clientActions.go
+++ b/clientActions.go
@@ -3,10 +3,9 @@ package gopher
import (
"github.com/gorilla/websocket"
"github.com/hewiefreeman/GopherGameServer/actions"
+ "github.com/hewiefreeman/GopherGameServer/core"
"github.com/hewiefreeman/GopherGameServer/database"
"github.com/hewiefreeman/GopherGameServer/helpers"
- "github.com/hewiefreeman/GopherGameServer/rooms"
- "github.com/hewiefreeman/GopherGameServer/users"
"sync"
)
@@ -14,7 +13,7 @@ const (
errorInvalidAction = "Invalid action"
errorLoggedIn = "You must be logged out"
errorNotLoggedIn = "You must be logged in"
- errorFeatureDisabled = "Server feature is disabled"
+ errorFeatureDisabled = "Server feature not enabled"
errorRoomControl = "Clients cannot control rooms"
errorServerRoom = "Clients cannot control that room type"
errorNotOwner = "You are not the owner of the room"
@@ -34,44 +33,44 @@ const (
errorIncorrectFormatVarKey = "Incorrect data format for variable key"
)
-func clientActionHandler(action clientAction, user **users.User, conn *websocket.Conn,
+func clientActionHandler(action clientAction, user **core.User, conn *websocket.Conn,
deviceTag *string, devicePass *string, deviceUserID *int, connID *string, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
- switch _action := action.A; _action {
+ switch action.A {
- // HIGH LOOK-UP PRIORITY ITEMS
+ // Custom actions and voice streams
case helpers.ClientActionCustomAction:
return clientCustomAction(action.P, user, conn, *connID, clientMux)
case helpers.ClientActionVoiceStream:
return clientActionVoiceStream(action.P, user, conn, *connID, clientMux)
- // USER VARIABLES
+ // User variables
case helpers.ClientActionSetVariable:
return clientActionSetVariable(action.P, user, *connID, clientMux)
case helpers.ClientActionSetVariables:
return clientActionSetVariables(action.P, user, *connID, clientMux)
- // CHAT
+ // Chat
case helpers.ClientActionChatMessage:
return clientActionChatMessage(action.P, user, *connID, clientMux)
case helpers.ClientActionPrivateMessage:
return clientActionPrivateMessage(action.P, user, *connID, clientMux)
- // CHANGE STATUS
+ // Change user status
case helpers.ClientActionChangeStatus:
return clientActionChangeStatus(action.P, user, clientMux)
- // LOGIN/LOGOUT
+ // Log in/out
case helpers.ClientActionLogin:
return clientActionLogin(action.P, user, deviceTag, devicePass, deviceUserID, conn, connID, clientMux)
case helpers.ClientActionLogout:
return clientActionLogout(user, deviceTag, devicePass, deviceUserID, connID, clientMux)
- // ROOM ACTIONS
+ // Room actions
case helpers.ClientActionJoinRoom:
return clientActionJoinRoom(action.P, user, *connID, clientMux)
@@ -86,7 +85,7 @@ func clientActionHandler(action clientAction, user **users.User, conn *websocket
case helpers.ClientActionRevokeInvite:
return clientActionRevokeInvite(action.P, user, *connID, clientMux)
- // FRIENDING
+ // Friending
case helpers.ClientActionFriendRequest:
return clientActionFriendRequest(action.P, user, clientMux)
@@ -97,7 +96,7 @@ func clientActionHandler(action clientAction, user **users.User, conn *websocket
case helpers.ClientActionRemoveFriend:
return clientActionRemoveFriend(action.P, user, clientMux)
- // DATABASE
+ // Database
case helpers.ClientActionSignup:
return clientActionSignup(action.P, user, clientMux)
@@ -108,7 +107,7 @@ func clientActionHandler(action clientAction, user **users.User, conn *websocket
case helpers.ClientActionChangeAccountInfo:
return clientActionChangeAccountInfo(action.P, user, clientMux)
- // INVALID CLIENT ACTION
+ // Invalid client action
default:
return nil, true, helpers.NewError(errorInvalidAction, helpers.ErrorGopherInvalidAction)
@@ -119,7 +118,7 @@ func clientActionHandler(action clientAction, user **users.User, conn *websocket
// CUSTOM CLIENT ACTIONS /////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
-func clientCustomAction(params interface{}, user **users.User, conn *websocket.Conn, connID string, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
+func clientCustomAction(params interface{}, user **core.User, conn *websocket.Conn, connID string, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
var ok bool
var pMap map[string]interface{}
var action string
@@ -133,14 +132,14 @@ func clientCustomAction(params interface{}, user **users.User, conn *websocket.C
userRef := *user
(*clientMux).Unlock()
actions.HandleCustomClientAction(action, pMap["d"], userRef, conn, connID)
- return nil, false, helpers.NewError("", 0)
+ return nil, false, helpers.NoError()
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
// CHANGE USER STATUS ////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
-func clientActionChangeStatus(params interface{}, user **users.User, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
+func clientActionChangeStatus(params interface{}, user **core.User, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
(*clientMux).Lock()
if *user == nil {
(*clientMux).Unlock()
@@ -148,7 +147,7 @@ func clientActionChangeStatus(params interface{}, user **users.User, clientMux *
}
userRef := *user
(*clientMux).Unlock()
- //GET PARAMS
+ // Get param map and extract values
var ok bool
var statusF float64
if statusF, ok = params.(float64); !ok {
@@ -158,14 +157,14 @@ func clientActionChangeStatus(params interface{}, user **users.User, clientMux *
//
userRef.SetStatus(status)
//
- return status, true, helpers.NewError("", 0)
+ return status, true, helpers.NoError()
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
// ACCOUNT/DATABASE ACTIONS //////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
-func clientActionSignup(params interface{}, user **users.User, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
+func clientActionSignup(params interface{}, user **core.User, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
(*clientMux).Lock()
if *user != nil {
(*clientMux).Unlock()
@@ -175,7 +174,7 @@ func clientActionSignup(params interface{}, user **users.User, clientMux *sync.M
return nil, true, helpers.NewError(errorFeatureDisabled, helpers.ErrorGopherFeatureDisabled)
}
(*clientMux).Unlock()
- //GET ITEMS FROM PARAMS
+ // Get param map and extract values
var ok bool
var pMap map[string]interface{}
var customCols map[string]interface{}
@@ -195,17 +194,17 @@ func clientActionSignup(params interface{}, user **users.User, clientMux *sync.M
if pass, ok = pMap["p"].(string); !ok {
return nil, true, helpers.NewError(errorIncorrectFormatPass, helpers.ErrorGopherPasswordFormat)
}
- //SIGN CLIENT UP
+ // Sign client up
signupErr := database.SignUpClient(userName, pass, customCols)
if signupErr.ID != 0 {
return nil, true, signupErr
}
//
- return nil, true, helpers.NewError("", 0)
+ return nil, true, helpers.NoError()
}
-func clientActionDeleteAccount(params interface{}, user **users.User, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
+func clientActionDeleteAccount(params interface{}, user **core.User, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
(*clientMux).Lock()
if *user != nil {
(*clientMux).Unlock()
@@ -215,7 +214,7 @@ func clientActionDeleteAccount(params interface{}, user **users.User, clientMux
return nil, true, helpers.NewError(errorFeatureDisabled, helpers.ErrorGopherFeatureDisabled)
}
(*clientMux).Unlock()
- //GET ITEMS FROM PARAMS
+ // Get param map and extract values
var ok bool
var pMap map[string]interface{}
var customCols map[string]interface{}
@@ -236,22 +235,22 @@ func clientActionDeleteAccount(params interface{}, user **users.User, clientMux
return nil, true, helpers.NewError(errorIncorrectFormatPass, helpers.ErrorGopherPasswordFormat)
}
- //CHECK IF USER IS ONLINE
- _, err := users.Get(userName)
+ // Check if online
+ _, err := core.GetUser(userName)
if err == nil {
return nil, true, helpers.NewError(err.Error(), helpers.ErrorGopherLoggedIn)
}
- //DELETE ACCOUNT
+ // Delete account
deleteErr := database.DeleteAccount(userName, pass, customCols)
if deleteErr.ID != 0 {
return nil, true, deleteErr
}
//
- return nil, true, helpers.NewError("", 0)
+ return nil, true, helpers.NoError()
}
-func clientActionChangePassword(params interface{}, user **users.User, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
+func clientActionChangePassword(params interface{}, user **core.User, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
(*clientMux).Lock()
if *user == nil {
(*clientMux).Unlock()
@@ -262,7 +261,7 @@ func clientActionChangePassword(params interface{}, user **users.User, clientMux
}
userRef := *user
(*clientMux).Unlock()
- //GET ITEMS FROM PARAMS
+ // Get param map and extract values
var ok bool
var pMap map[string]interface{}
var customCols map[string]interface{}
@@ -282,17 +281,17 @@ func clientActionChangePassword(params interface{}, user **users.User, clientMux
if newPass, ok = pMap["n"].(string); !ok {
return nil, true, helpers.NewError(errorIncorrectFormatNewPass, helpers.ErrorGopherNewPasswordFormat)
}
- //CHANGE PASSWORD
+ // Change password
changeErr := database.ChangePassword(userRef.Name(), pass, newPass, customCols)
if changeErr.ID != 0 {
return nil, true, changeErr
}
//
- return nil, true, helpers.NewError("", 0)
+ return nil, true, helpers.NoError()
}
-func clientActionChangeAccountInfo(params interface{}, user **users.User, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
+func clientActionChangeAccountInfo(params interface{}, user **core.User, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
(*clientMux).Lock()
if *user == nil {
(*clientMux).Unlock()
@@ -303,7 +302,7 @@ func clientActionChangeAccountInfo(params interface{}, user **users.User, client
}
userRef := *user
(*clientMux).Unlock()
- //GET ITEMS FROM PARAMS
+ // Get param map and extract values
var ok bool
var pMap map[string]interface{}
var customCols map[string]interface{}
@@ -319,29 +318,29 @@ func clientActionChangeAccountInfo(params interface{}, user **users.User, client
if pass, ok = pMap["p"].(string); !ok {
return nil, true, helpers.NewError(errorIncorrectFormatPass, helpers.ErrorGopherPasswordFormat)
}
- //CHANGE ACCOUNT INFO
+ // Change account info
changeErr := database.ChangeAccountInfo(userRef.Name(), pass, customCols)
if changeErr.ID != 0 {
return nil, true, changeErr
}
//
- return nil, true, helpers.NewError("", 0)
+ return nil, true, helpers.NoError()
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
// LOGIN+LOGOUT ACTIONS //////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
-func clientActionLogin(params interface{}, user **users.User, deviceTag *string, devicePass *string, deviceUserID *int, conn *websocket.Conn,
+func clientActionLogin(params interface{}, user **core.User, deviceTag *string, devicePass *string, deviceUserID *int, conn *websocket.Conn,
connID *string, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
(*clientMux).Lock()
if *user != nil {
- defer (*clientMux).Unlock()
+ (*clientMux).Unlock()
return nil, true, helpers.NewError(errorLoggedIn, helpers.ErrorGopherLoggedIn)
}
(*clientMux).Unlock()
- //MAKE A MAP FROM PARAMS
+ // Get param map and extract values
var ok bool
var pMap map[string]interface{}
var name string
@@ -355,11 +354,11 @@ func clientActionLogin(params interface{}, user **users.User, deviceTag *string,
if name, ok = pMap["n"].(string); !ok {
return nil, true, helpers.NewError(errorIncorrectFormatName, helpers.ErrorGopherNameFormat)
}
- if (*settings).EnableSqlFeatures {
+ if settings.EnableSqlFeatures {
if pass, ok = pMap["p"].(string); !ok {
return nil, true, helpers.NewError(errorIncorrectFormatPass, helpers.ErrorGopherPasswordFormat)
}
- if (*settings).RememberMe {
+ if settings.RememberMe {
if remMe, ok = pMap["r"].(bool); !ok {
return nil, true, helpers.NewError(errorIncorrectFormatRemember, helpers.ErrorGopherRememberFormat)
}
@@ -375,34 +374,46 @@ func clientActionLogin(params interface{}, user **users.User, deviceTag *string,
return nil, true, helpers.NewError(errorIncorrectFormatCols, helpers.ErrorGopherColumnsFormat)
}
}
- //LOG IN
+ // Log socket in
var dbIndex int
- var uName string
var dPass string
var cID string
var err helpers.GopherError
- if (*settings).EnableSqlFeatures {
- uName, dbIndex, dPass, err = database.LoginClient(name, pass, *deviceTag, remMe, customCols)
- if err.ID != 0 {
- return nil, true, err
- }
- cID, err = users.Login(uName, dbIndex, dPass, guest, remMe, conn, user, clientMux)
- } else {
- cID, err = users.Login(name, -1, "", guest, false, conn, user, clientMux)
- }
- if err.ID != 0 {
- return nil, true, err
+ if dbIndex, dPass, cID, err = loginClient(settings, guest, name, pass, *deviceTag, remMe, customCols, user,
+ conn, clientMux); err.ID != 0 {
+ return nil, false, err
}
- //CHANGE SOCKET'S userName
+
+ // Update socket
*devicePass = dPass
*deviceUserID = dbIndex
*connID = cID
//
- return nil, false, helpers.NewError("", 0)
+ return nil, false, helpers.NoError()
+}
+
+func loginClient(s *ServerSettings, guest bool, name string, pass string, deviceTag string, remMe bool,
+ customCols map[string]interface{}, user **core.User, conn *websocket.Conn, clientMux *sync.Mutex) (int, string, string, helpers.GopherError) {
+ var dbIndex int
+ var dPass string
+ var cID string
+ var err helpers.GopherError
+ if s.EnableSqlFeatures && !guest {
+ var uName string
+ uName, dbIndex, dPass, err = database.LoginClient(name, pass, deviceTag, remMe, customCols)
+ if err.ID != 0 {
+ return 0, "", "", err
+ }
+ cID, err = core.Login(uName, dbIndex, dPass, guest, remMe, conn, user, clientMux)
+ } else {
+ cID, err = core.Login(name, -1, "", guest, false, conn, user, clientMux)
+ }
+
+ return dbIndex, dPass, cID, err
}
-func clientActionLogout(user **users.User, deviceTag *string, devicePass *string, deviceUserID *int, connID *string, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
+func clientActionLogout(user **core.User, deviceTag *string, devicePass *string, deviceUserID *int, connID *string, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
(*clientMux).Lock()
if *user == nil {
(*clientMux).Unlock()
@@ -410,26 +421,26 @@ func clientActionLogout(user **users.User, deviceTag *string, devicePass *string
}
userRef := *user
(*clientMux).Unlock()
- //LOG User OUT AND RESET
+ // Log user out
userRef.Logout(*connID)
- //REMOVE AUTO-LOG IF ANY
+ // Remove any auto-logins for this device tag
if (*settings).EnableSqlFeatures && (*settings).RememberMe {
database.RemoveAutoLog(*deviceUserID, *deviceTag)
}
- //
+ // Update socket
*devicePass = ""
*deviceUserID = 0
*connID = ""
//
- return nil, false, helpers.NewError("", 0)
+ return nil, false, helpers.NoError()
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
// ROOM ACTIONS //////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
-func clientActionJoinRoom(params interface{}, user **users.User, connID string, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
+func clientActionJoinRoom(params interface{}, user **core.User, connID string, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
(*clientMux).Lock()
if *user == nil {
(*clientMux).Unlock()
@@ -437,28 +448,28 @@ func clientActionJoinRoom(params interface{}, user **users.User, connID string,
}
userRef := *user
(*clientMux).Unlock()
- //GET ROOM NAME FROM PARAMS
+ // Get room name from params
var ok bool
var roomName string
if roomName, ok = params.(string); !ok {
return nil, true, helpers.NewError(errorIncorrectFormat, helpers.ErrorGopherIncorrectFormat)
}
- //GET ROOM
- room, roomErr := rooms.Get(roomName)
+ // Get room
+ room, roomErr := core.GetRoom(roomName)
if roomErr != nil {
return nil, true, helpers.NewError(roomErr.Error(), helpers.ErrorGopherJoin)
}
- //MAKE User JOIN THE Room
+ // Make user join the room
joinErr := userRef.Join(room, connID)
if joinErr != nil {
return nil, true, helpers.NewError(joinErr.Error(), helpers.ErrorGopherJoin)
}
//
- return nil, true, helpers.NewError("", 0)
+ return nil, false, helpers.NoError()
}
-func clientActionLeaveRoom(user **users.User, connID string, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
+func clientActionLeaveRoom(user **core.User, connID string, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
(*clientMux).Lock()
if *user == nil {
(*clientMux).Unlock()
@@ -466,17 +477,17 @@ func clientActionLeaveRoom(user **users.User, connID string, clientMux *sync.Mut
}
userRef := *user
(*clientMux).Unlock()
- //MAKE USER LEAVE ROOM
+ // Make user leave room
leaveErr := userRef.Leave(connID)
if leaveErr != nil {
return nil, true, helpers.NewError(leaveErr.Error(), helpers.ErrorGopherLeave)
}
//
- return nil, true, helpers.NewError("", 0)
+ return nil, false, helpers.NoError()
}
-func clientActionCreateRoom(params interface{}, user **users.User, connID string, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
+func clientActionCreateRoom(params interface{}, user **core.User, connID string, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
(*clientMux).Lock()
if *user == nil {
(*clientMux).Unlock()
@@ -487,7 +498,7 @@ func clientActionCreateRoom(params interface{}, user **users.User, connID string
}
userRef := *user
(*clientMux).Unlock()
- //GET PARAMS
+ // Get param map and extract values
var ok bool
var pMap map[string]interface{}
var roomName string
@@ -510,33 +521,28 @@ func clientActionCreateRoom(params interface{}, user **users.User, connID string
return nil, true, helpers.NewError(errorIncorrectFormatMaxRoomUsers, helpers.ErrorGopherMaxRoomFormat)
}
maxUsers := int(maxUsersF)
- //
- if rType, ok := rooms.GetRoomTypes()[roomType]; !ok {
+ // Verify type
+ if rType, ok := core.GetRoomTypes()[roomType]; !ok {
return nil, true, helpers.NewError(errorRoomType, helpers.ErrorGopherMaxRoomFormat)
} else if rType.ServerOnly() {
return nil, true, helpers.NewError(errorServerRoom, helpers.ErrorGopherServerRoom)
}
- //CHECK IF USER IS IN A ROOM
- /*currRoom := user.RoomIn()
- if currRoom != nil && currRoom.Name() != "" {
- return nil, true, errors.New("You must leave your current room to create a room")
- }*/
- //MAKE THE Room
- room, roomErr := rooms.New(roomName, roomType, private, maxUsers, userRef.Name())
+ // Make the room
+ room, roomErr := core.NewRoom(roomName, roomType, private, maxUsers, userRef.Name())
if roomErr != nil {
return nil, true, helpers.NewError(roomErr.Error(), helpers.ErrorGopherCreateRoom)
}
- //ADD THE User TO THE ROOM
+ // Add user to the new room
joinErr := userRef.Join(room, connID)
if joinErr != nil {
return nil, true, helpers.NewError(joinErr.Error(), helpers.ErrorGopherJoin)
}
//
- return roomName, true, helpers.NewError("", 0)
+ return roomName, true, helpers.NoError()
}
-func clientActionDeleteRoom(params interface{}, user **users.User, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
+func clientActionDeleteRoom(params interface{}, user **core.User, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
(*clientMux).Lock()
if *user == nil {
(*clientMux).Unlock()
@@ -547,34 +553,34 @@ func clientActionDeleteRoom(params interface{}, user **users.User, clientMux *sy
}
userRef := *user
(*clientMux).Unlock()
- //GET PARAMS
+ // Get room name from params
var ok bool
var roomName string
if roomName, ok = params.(string); !ok {
return nil, true, helpers.NewError(errorIncorrectFormat, helpers.ErrorGopherIncorrectFormat)
}
- //GET ROOM
- room, roomErr := rooms.Get(roomName)
+ // Get room
+ room, roomErr := core.GetRoom(roomName)
if roomErr != nil {
return nil, true, helpers.NewError(roomErr.Error(), helpers.ErrorGopherDeleteRoom)
} else if room.Owner() != userRef.Name() {
return nil, true, helpers.NewError(errorNotOwner, helpers.ErrorGopherNotOwner)
}
//
- rType := rooms.GetRoomTypes()[room.Type()]
+ rType := core.GetRoomTypes()[room.Type()]
if rType.ServerOnly() {
return nil, true, helpers.NewError(errorServerRoom, helpers.ErrorGopherServerRoom)
}
- //DELETE ROOM
+ // Delete the room
deleteErr := room.Delete()
if deleteErr != nil {
return nil, true, helpers.NewError(deleteErr.Error(), helpers.ErrorGopherDeleteRoom)
}
- return nil, true, helpers.NewError("", 0)
+ return roomName, true, helpers.NoError()
}
-func clientActionRoomInvite(params interface{}, user **users.User, connID string, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
+func clientActionRoomInvite(params interface{}, user **core.User, connID string, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
(*clientMux).Lock()
if *user == nil {
(*clientMux).Unlock()
@@ -585,27 +591,27 @@ func clientActionRoomInvite(params interface{}, user **users.User, connID string
}
userRef := *user
(*clientMux).Unlock()
- //GET PARAMS
+ // Get name from params
var ok bool
var name string
if name, ok = params.(string); !ok {
return nil, true, helpers.NewError(errorIncorrectFormat, helpers.ErrorGopherIncorrectFormat)
}
- //GET INVITED USER
- invUser, invUserErr := users.Get(name)
+ // Get invited user
+ invUser, invUserErr := core.GetUser(name)
if invUserErr != nil {
return nil, true, helpers.NewError(invUserErr.Error(), helpers.ErrorGopherInvite)
}
- //INVITE
+ // Invite user
invUserErr = userRef.Invite(invUser, connID)
if invUserErr != nil {
return nil, true, helpers.NewError(invUserErr.Error(), helpers.ErrorGopherInvite)
}
//
- return nil, true, helpers.NewError("", 0)
+ return nil, true, helpers.NoError()
}
-func clientActionRevokeInvite(params interface{}, user **users.User, connID string, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
+func clientActionRevokeInvite(params interface{}, user **core.User, connID string, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
(*clientMux).Lock()
if *user == nil {
(*clientMux).Unlock()
@@ -616,152 +622,147 @@ func clientActionRevokeInvite(params interface{}, user **users.User, connID stri
}
userRef := *user
(*clientMux).Unlock()
- //GET PARAMS
+ // Get name from params
var ok bool
var name string
if name, ok = params.(string); !ok {
return nil, true, helpers.NewError(errorIncorrectFormat, helpers.ErrorGopherIncorrectFormat)
}
- //REVOKE INVITE
+ // Revoke invite
revokeErr := userRef.RevokeInvite(name, connID)
if revokeErr != nil {
return nil, true, helpers.NewError(revokeErr.Error(), helpers.ErrorGopherRevokeInvite)
}
//
- return nil, true, helpers.NewError("", 0)
+ return nil, true, helpers.NoError()
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
// CHAT+VOICE ACTIONS ////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
-func clientActionVoiceStream(params interface{}, user **users.User, conn *websocket.Conn, connID string, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
+func clientActionVoiceStream(params interface{}, user **core.User, conn *websocket.Conn, connID string, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
(*clientMux).Lock()
if *user == nil {
(*clientMux).Unlock()
- return nil, false, helpers.NewError("", 0)
+ return nil, false, helpers.NoError()
}
userRef := *user
(*clientMux).Unlock()
- //GET CURRENT ROOM
+ // Get current room
currRoom := userRef.RoomIn(connID)
if currRoom == nil || currRoom.Name() == "" {
- return nil, false, helpers.NewError("", 0)
+ return nil, false, helpers.NoError()
}
- //CHECK IF VOICE CHAT ROOM
- rType := rooms.GetRoomTypes()[currRoom.Type()]
+ // Check for voice chat
+ rType := core.GetRoomTypes()[currRoom.Type()]
if !rType.VoiceChatEnabled() {
- return nil, false, helpers.NewError("", 0)
+ return nil, false, helpers.NoError()
}
- //SEND VOICE STREAM
+ // Send voice stream
currRoom.VoiceStream(userRef.Name(), conn, params)
//
- return nil, false, helpers.NewError("", 0)
+ return nil, false, helpers.NoError()
}
-func clientActionChatMessage(params interface{}, user **users.User, connID string, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
+func clientActionChatMessage(params interface{}, user **core.User, connID string, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
(*clientMux).Lock()
if *user == nil {
(*clientMux).Unlock()
- return nil, false, helpers.NewError("", 0)
+ return nil, false, helpers.NoError()
}
userRef := *user
(*clientMux).Unlock()
- //GET CURRENT ROOM
+ // Get current room
currRoom := userRef.RoomIn(connID)
if currRoom == nil || currRoom.Name() == "" {
- return nil, false, helpers.NewError("", 0)
+ return nil, false, helpers.NoError()
}
- //SEND CHAT MESSAGE
+ // Send chat message
currRoom.ChatMessage(userRef.Name(), params)
//
- return nil, false, helpers.NewError("", 0)
+ return nil, false, helpers.NoError()
}
-func clientActionPrivateMessage(params interface{}, user **users.User, connID string, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
+func clientActionPrivateMessage(params interface{}, user **core.User, connID string, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
(*clientMux).Lock()
if *user == nil {
(*clientMux).Unlock()
- return nil, false, helpers.NewError("", 0)
+ return nil, false, helpers.NoError()
}
userRef := *user
(*clientMux).Unlock()
- //GET PARAMS
+ // Get param map and extract values
var ok bool
var pMap map[string]interface{}
var userName string
if pMap, ok = params.(map[string]interface{}); !ok {
- return nil, false, helpers.NewError("", 0)
+ return nil, false, helpers.NoError()
}
if userName, ok = pMap["u"].(string); !ok {
- return nil, false, helpers.NewError("", 0)
- }
- //GET CURRENT ROOM
- currRoom := userRef.RoomIn(connID)
- if currRoom == nil || currRoom.Name() == "" {
- return nil, false, helpers.NewError("", 0)
+ return nil, false, helpers.NoError()
}
- //SEND CHAT MESSAGE
+ // Send chat message
userRef.PrivateMessage(userName, pMap["m"])
//
- return nil, false, helpers.NewError("", 0)
+ return nil, false, helpers.NoError()
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
// USER VARIABLES ////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
-func clientActionSetVariable(params interface{}, user **users.User, connID string, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
+func clientActionSetVariable(params interface{}, user **core.User, connID string, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
(*clientMux).Lock()
if *user == nil {
(*clientMux).Unlock()
- return nil, false, helpers.NewError("", 0)
+ return nil, false, helpers.NoError()
}
userRef := *user
(*clientMux).Unlock()
- //GET PARAMS
+ // Get param map and extract values
var ok bool
var pMap map[string]interface{}
var varKey string
var varVal interface{}
if pMap, ok = params.(map[string]interface{}); !ok {
- return nil, false, helpers.NewError("", 0)
+ return nil, false, helpers.NoError()
}
if varKey, ok = pMap["k"].(string); !ok {
- return nil, false, helpers.NewError("", 0)
+ return nil, false, helpers.NoError()
}
varVal = pMap["v"]
- //SET THE VARIABLE
+ // Set the variable
userRef.SetVariable(varKey, varVal, connID)
//
- return nil, false, helpers.NewError("", 0)
+ return nil, false, helpers.NoError()
}
-func clientActionSetVariables(params interface{}, user **users.User, connID string, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
+func clientActionSetVariables(params interface{}, user **core.User, connID string, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
(*clientMux).Lock()
if *user == nil {
(*clientMux).Unlock()
- return nil, false, helpers.NewError("", 0)
+ return nil, false, helpers.NoError()
}
userRef := *user
(*clientMux).Unlock()
- //GET PARAMS
+ // Get variables map from params
var ok bool
var pMap map[string]interface{}
if pMap, ok = params.(map[string]interface{}); !ok {
- return nil, false, helpers.NewError("", 0)
+ return nil, false, helpers.NoError()
}
//SET THE VARIABLES
userRef.SetVariables(pMap, connID)
//
- return nil, false, helpers.NewError("", 0)
+ return nil, false, helpers.NoError()
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
// FRIENDING ACTIONS /////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
-func clientActionFriendRequest(params interface{}, user **users.User, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
+func clientActionFriendRequest(params interface{}, user **core.User, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
(*clientMux).Lock()
if *user == nil {
(*clientMux).Unlock()
@@ -772,7 +773,7 @@ func clientActionFriendRequest(params interface{}, user **users.User, clientMux
}
userRef := *user
(*clientMux).Unlock()
- //GET PARAMS AS A MAP
+ // Get name from params
var ok bool
var friendName string
@@ -786,10 +787,10 @@ func clientActionFriendRequest(params interface{}, user **users.User, clientMux
}
//
- return nil, false, helpers.NewError("", 0)
+ return nil, false, helpers.NoError()
}
-func clientActionAcceptFriend(params interface{}, user **users.User, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
+func clientActionAcceptFriend(params interface{}, user **core.User, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
(*clientMux).Lock()
if *user == nil {
(*clientMux).Unlock()
@@ -800,7 +801,7 @@ func clientActionAcceptFriend(params interface{}, user **users.User, clientMux *
}
userRef := *user
(*clientMux).Unlock()
- //GET PARAMS AS A MAP
+ // Get name from params
var ok bool
var friendName string
@@ -814,10 +815,10 @@ func clientActionAcceptFriend(params interface{}, user **users.User, clientMux *
}
//
- return nil, false, helpers.NewError("", 0)
+ return nil, false, helpers.NoError()
}
-func clientActionDeclineFriend(params interface{}, user **users.User, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
+func clientActionDeclineFriend(params interface{}, user **core.User, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
(*clientMux).Lock()
if *user == nil {
(*clientMux).Unlock()
@@ -828,7 +829,7 @@ func clientActionDeclineFriend(params interface{}, user **users.User, clientMux
}
userRef := *user
(*clientMux).Unlock()
- //GET PARAMS AS A MAP
+ // Get name from params
var ok bool
var friendName string
@@ -842,10 +843,10 @@ func clientActionDeclineFriend(params interface{}, user **users.User, clientMux
}
//
- return nil, false, helpers.NewError("", 0)
+ return nil, false, helpers.NoError()
}
-func clientActionRemoveFriend(params interface{}, user **users.User, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
+func clientActionRemoveFriend(params interface{}, user **core.User, clientMux *sync.Mutex) (interface{}, bool, helpers.GopherError) {
(*clientMux).Lock()
if *user == nil {
(*clientMux).Unlock()
@@ -856,7 +857,7 @@ func clientActionRemoveFriend(params interface{}, user **users.User, clientMux *
}
userRef := *user
(*clientMux).Unlock()
- //GET PARAMS AS A MAP
+ // Get name from params
var ok bool
var friendName string
@@ -870,5 +871,5 @@ func clientActionRemoveFriend(params interface{}, user **users.User, clientMux *
}
//
- return nil, false, helpers.NewError("", 0)
+ return nil, false, helpers.NoError()
}
diff --git a/core/core.go b/core/core.go
new file mode 100644
index 0000000..8b6210f
--- /dev/null
+++ b/core/core.go
@@ -0,0 +1,143 @@
+// Package core contains all the tools to make and work with Users and Rooms.
+//
+// A User is a client who has successfully logged into the server. You can think of clients who are not attached to a User
+// as, for instance, someone in the login screen, but are still connected to the server. A client doesn't
+// have to be a User to be able to call your CustomClientActions, so keep that in mind when making them (Refer to the Usage for CustomClientActions).
+//
+// Users have their own variables which can be accessed and changed anytime. A User variable can
+// be anything compatible with interface{}, so pretty much anything.
+//
+// A Room represents a place on the server where a User can join other Users. Rooms can either be public or private. Private Rooms must be assigned an "owner", which is the name of a User, or the ServerName
+// from ServerSettings. The server's name that will be used for ownership of private Rooms can be set with the ServerSettings
+// option ServerName when starting the server. Though keep in mind, setting the ServerName in ServerSettings will prevent a User who wants to go by that name
+// from logging in. Public Rooms will accept a join request from any User, and private Rooms will only
+// accept a join request from someone who is on it's invite list. Only the owner of the Room or the server itself can invite
+// Users to a private Room. But remember, just because a User owns a private room doesn't mean the server cannot also invite
+// to the room via *Room.AddInvite() function.
+//
+// Rooms have their own variables which can be accessed and changed anytime. Like User variables, a Room variable can
+// be anything compatible with interface{}.
+package core
+
+import (
+ "github.com/hewiefreeman/GopherGameServer/helpers"
+)
+
+var (
+ serverStarted bool
+ serverPaused bool
+
+ serverName string
+ kickOnLogin bool
+ sqlFeatures bool
+ rememberMe bool
+ multiConnect bool
+ maxUserConns uint8
+ deleteRoomOnLeave bool = true
+)
+
+// RoomRecoveryState is used internally for persisting room states on shutdown.
+type RoomRecoveryState struct {
+ T string // rType
+ P bool // private
+ O string // owner
+ M int // maxUsers
+ I []string // inviteList
+ V map[string]interface{} // vars
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+// SERVER STARTUP FUNCTIONS //////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// SetServerStarted is for Gopher Game Server internal mechanics only.
+func SetServerStarted(val bool) {
+ if !serverStarted {
+ serverStarted = val
+ }
+}
+
+// SettingsSet is for Gopher Game Server internal mechanics only.
+func SettingsSet(kickDups bool, name string, deleteOnLeave bool, sqlFeat bool, remMe bool, multiConn bool, maxConns uint8) {
+ if !serverStarted {
+ kickOnLogin = kickDups
+ serverName = name
+ sqlFeatures = sqlFeat
+ rememberMe = remMe
+ multiConnect = multiConn
+ maxUserConns = maxConns
+ deleteRoomOnLeave = deleteOnLeave
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+// SERVER PAUSE AND RESUME ///////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Pause is only for internal Gopher Game Server mechanics.
+func Pause() {
+ if !serverPaused {
+ serverPaused = true
+
+ //
+ clientResp := helpers.MakeClientResponse(helpers.ClientActionLogout, nil, helpers.NoError())
+ usersMux.Lock()
+ for _, user := range users {
+ user.mux.Lock()
+ for connID, conn := range user.conns {
+ //REMOVE CONNECTION FROM THEIR ROOM
+ currRoom := conn.room
+ if currRoom != nil && currRoom.Name() != "" {
+ user.mux.Unlock()
+ currRoom.RemoveUser(user, connID)
+ user.mux.Lock()
+ }
+
+ //LOG CONNECTION OUT
+ conn.clientMux.Lock()
+ if *(conn.user) != nil {
+ *(conn.user) = nil
+ }
+ conn.clientMux.Unlock()
+
+ //SEND LOG OUT MESSAGE
+ conn.socket.WriteJSON(clientResp)
+ }
+ user.mux.Unlock()
+ }
+ users = make(map[string]*User)
+ usersMux.Unlock()
+ }
+}
+
+// Resume is only for internal Gopher Game Server mechanics.
+func Resume() {
+ if serverPaused {
+ serverPaused = false
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// GET STATES FOR GENERATING RECOVERY FILE ////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// GetRoomsState is only for internal Gopher Game Server mechanics.
+func GetRoomsState() map[string]RoomRecoveryState {
+ state := make(map[string]RoomRecoveryState)
+ roomsMux.Lock()
+ for _, room := range rooms {
+ room.mux.Lock()
+ state[room.name] = RoomRecoveryState{
+ T: room.rType,
+ P: room.private,
+ O: room.owner,
+ M: room.maxUsers,
+ I: room.inviteList,
+ V: room.vars,
+ }
+ room.mux.Unlock()
+ }
+ roomsMux.Unlock()
+ //
+ return state
+}
diff --git a/users/friending.go b/core/friending.go
similarity index 80%
rename from users/friending.go
rename to core/friending.go
index 32f6c7b..9c63023 100644
--- a/users/friending.go
+++ b/core/friending.go
@@ -1,4 +1,4 @@
-package users
+package core
import (
"errors"
@@ -16,7 +16,7 @@ func (u *User) FriendRequest(friendName string) error {
return errors.New("The user '" + friendName + "' cannot be requested as a friend")
}
//CHECK IF FRIEND IS ONLINE & GET DATABASE ID
- friend, friendErr := Get(friendName)
+ friend, friendErr := GetUser(friendName)
var friendOnline bool = false
var friendID int
if friendErr != nil {
@@ -50,9 +50,11 @@ func (u *User) FriendRequest(friendName string) error {
//SEND A FRIEND REQUEST TO THE USER IF THEY ARE ONLINE
if friendOnline {
- message := make(map[string]interface{})
- message[helpers.ServerActionFriendRequest] = make(map[string]interface{})
- message[helpers.ServerActionFriendRequest].(map[string]interface{})["n"] = u.name
+ message := map[string]map[string]interface{}{
+ helpers.ServerActionFriendRequest: {
+ "n": u.name,
+ },
+ }
friend.mux.Lock()
for _, conn := range friend.conns {
(*conn).socket.WriteJSON(message)
@@ -61,7 +63,7 @@ func (u *User) FriendRequest(friendName string) error {
}
//SEND RESPONSE TO CLIENT
- clientResp := helpers.MakeClientResponse(helpers.ClientActionFriendRequest, friendName, helpers.NewError("", 0))
+ clientResp := helpers.MakeClientResponse(helpers.ClientActionFriendRequest, friendName, helpers.NoError())
u.mux.Lock()
for _, conn := range u.conns {
(*conn).socket.WriteJSON(clientResp)
@@ -84,7 +86,7 @@ func (u *User) AcceptFriendRequest(friendName string) error {
return errors.New("The user '" + friendName + "' cannot be accepted as a friend")
}
//CHECK IF FRIEND IS ONLINE & GET DATABASE ID
- friend, friendErr := Get(friendName)
+ friend, friendErr := GetUser(friendName)
var friendOnline bool = false
var friendID int
if friendErr != nil {
@@ -114,28 +116,31 @@ func (u *User) AcceptFriendRequest(friendName string) error {
return errors.New("Unexpected friend error")
}
- //SEND A FRIEND REQUEST TO THE USER IF THEY ARE ONLINE
- var status int = StatusOffline
+ //SEND ACCEPT MESSAGE TO THE USER IF THEY ARE ONLINE
+ var fStatus int = StatusOffline
if friendOnline {
- message := make(map[string]interface{})
- message[helpers.ServerActionFriendAccept] = make(map[string]interface{})
- message[helpers.ServerActionFriendAccept].(map[string]interface{})["n"] = u.name
- message[helpers.ServerActionFriendAccept].(map[string]interface{})["s"] = u.status
+ message := map[string]map[string]interface{}{
+ helpers.ServerActionFriendAccept: {
+ "n": u.name,
+ "s": u.status,
+ },
+ }
friend.mux.Lock()
for _, conn := range friend.conns {
(*conn).socket.WriteJSON(message)
}
- status = friend.status
+ fStatus = friend.status
friend.mux.Unlock()
}
//MAKE RESPONSE
- responseMap := make(map[string]interface{})
- responseMap["n"] = friendName
- responseMap["s"] = status
+ responseMap := map[string]interface{}{
+ "n": friendName,
+ "s": fStatus,
+ }
- //SEND RESPONSE TO CLIENT
- clientResp := helpers.MakeClientResponse(helpers.ClientActionAcceptFriend, responseMap, helpers.NewError("", 0))
+ //SEND RESPONSE TO ALL CLIENT CONNECTIONS
+ clientResp := helpers.MakeClientResponse(helpers.ClientActionAcceptFriend, responseMap, helpers.NoError())
u.mux.Lock()
for _, conn := range u.conns {
(*conn).socket.WriteJSON(clientResp)
@@ -147,7 +152,7 @@ func (u *User) AcceptFriendRequest(friendName string) error {
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
-// DECLINE A FRIEND REQUEST //////////////////////////////////////////////////////////////////////
+// Decline friend request ////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
// DeclineFriendRequest declines a friend request from another User by their name.
@@ -158,7 +163,7 @@ func (u *User) DeclineFriendRequest(friendName string) error {
return errors.New("The user '" + friendName + "' cannot be declined as a friend")
}
//CHECK IF FRIEND IS ONLINE & GET DATABASE ID
- friend, friendErr := Get(friendName)
+ friend, friendErr := GetUser(friendName)
var friendOnline bool = false
var friendID int
if friendErr != nil {
@@ -191,9 +196,11 @@ func (u *User) DeclineFriendRequest(friendName string) error {
//SEND A FRIEND REQUEST TO THE USER IF THEY ARE ONLINE
if friendOnline {
- message := make(map[string]interface{})
- message[helpers.ServerActionFriendRemove] = make(map[string]interface{})
- message[helpers.ServerActionFriendRemove].(map[string]interface{})["n"] = u.name
+ message := map[string]map[string]interface{}{
+ helpers.ServerActionFriendRemove: {
+ "n": u.name,
+ },
+ }
friend.mux.Lock()
for _, conn := range friend.conns {
(*conn).socket.WriteJSON(message)
@@ -202,7 +209,7 @@ func (u *User) DeclineFriendRequest(friendName string) error {
}
//SEND RESPONSE TO CLIENT
- clientResp := helpers.MakeClientResponse(helpers.ClientActionDeclineFriend, friendName, helpers.NewError("", 0))
+ clientResp := helpers.MakeClientResponse(helpers.ClientActionDeclineFriend, friendName, helpers.NoError())
u.mux.Lock()
for _, conn := range u.conns {
(*conn).socket.WriteJSON(clientResp)
@@ -214,7 +221,7 @@ func (u *User) DeclineFriendRequest(friendName string) error {
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
-// REMOVE A FRIEND ///////////////////////////////////////////////////////////////////////////////
+// Remove a friend ///////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
// RemoveFriend removes a friend from this this User and this User from the friend's Friend list.
@@ -225,7 +232,7 @@ func (u *User) RemoveFriend(friendName string) error {
return errors.New("The user '" + friendName + "' cannot be removed as a friend")
}
//CHECK IF FRIEND IS ONLINE & GET DATABASE ID
- friend, friendErr := Get(friendName)
+ friend, friendErr := GetUser(friendName)
var friendOnline bool = false
var friendID int
if friendErr != nil {
@@ -258,9 +265,11 @@ func (u *User) RemoveFriend(friendName string) error {
//SEND A FRIEND REQUEST TO THE USER IF THEY ARE ONLINE
if friendOnline {
- message := make(map[string]interface{})
- message[helpers.ServerActionFriendRemove] = make(map[string]interface{})
- message[helpers.ServerActionFriendRemove].(map[string]interface{})["n"] = u.name
+ message := map[string]map[string]interface{}{
+ helpers.ServerActionFriendRemove: {
+ "n": u.name,
+ },
+ }
friend.mux.Lock()
for _, conn := range friend.conns {
(*conn).socket.WriteJSON(message)
@@ -269,7 +278,7 @@ func (u *User) RemoveFriend(friendName string) error {
}
//SEND RESPONSE TO CLIENT
- clientResp := helpers.MakeClientResponse(helpers.ClientActionRemoveFriend, friendName, helpers.NewError("", 0))
+ clientResp := helpers.MakeClientResponse(helpers.ClientActionRemoveFriend, friendName, helpers.NoError())
u.mux.Lock()
for _, conn := range u.conns {
(*conn).socket.WriteJSON(clientResp)
@@ -279,3 +288,22 @@ func (u *User) RemoveFriend(friendName string) error {
//
return nil
}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+// Send message to all friends ///////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+
+func (u *User) sendToFriends(message interface{}) {
+ for key, val := range u.friends {
+ if val.RequestStatus() == database.FriendStatusAccepted {
+ friend, friendErr := GetUser(key)
+ if friendErr == nil {
+ friend.mux.Lock()
+ for _, friendConn := range friend.conns {
+ (*friendConn).socket.WriteJSON(message)
+ }
+ friend.mux.Unlock()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/messaging.go b/core/messaging.go
new file mode 100644
index 0000000..102d09e
--- /dev/null
+++ b/core/messaging.go
@@ -0,0 +1,297 @@
+package core
+
+import (
+ "errors"
+ "github.com/gorilla/websocket"
+ "github.com/hewiefreeman/GopherGameServer/helpers"
+)
+
+// These represent the types of room messages the server sends.
+const (
+ MessageTypeChat = iota
+ MessageTypeServer
+)
+
+// These are the sub-types that a MessageTypeServer will come with. Ordered by their visible priority for your UI.
+const (
+ ServerMessageGame = iota
+ ServerMessageNotice
+ ServerMessageImportant
+)
+
+var (
+ privateMessageCallback func(*User, *User, interface{})
+ privateMessageCallbackSet bool
+ chatMessageCallback func(string, *Room, interface{})
+ chatMessageCallbackSet bool
+ serverMessageCallback func(*Room, int, interface{})
+ serverMessageCallbackSet bool
+)
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+// Messaging Users ///////////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// PrivateMessage sends a private message to another User by name.
+func (u *User) PrivateMessage(userName string, message interface{}) {
+ user, userErr := GetUser(userName)
+ if userErr != nil {
+ return
+ }
+
+ //CONSTRUCT MESSAGE
+ theMessage := map[string]map[string]interface{}{
+ helpers.ServerActionPrivateMessage: {
+ "f": u.name, // from
+ "t": user.name, // to
+ "m": message,
+ },
+ }
+
+ //SEND MESSAGES
+ user.mux.Lock()
+ for _, conn := range user.conns {
+ (*conn).socket.WriteJSON(theMessage)
+ }
+ user.mux.Unlock()
+ u.mux.Lock()
+ for _, conn := range u.conns {
+ (*conn).socket.WriteJSON(theMessage)
+ }
+ u.mux.Unlock()
+
+ if privateMessageCallbackSet {
+ privateMessageCallback(u, user, message)
+ }
+
+ return
+}
+
+// DataMessage sends a data message directly to the User.
+func (u *User) DataMessage(data interface{}, connID string) {
+ //CONSTRUCT MESSAGE
+ message := map[string]interface{}{
+ helpers.ServerActionDataMessage: data,
+ }
+
+ //SEND MESSAGE TO USER
+ u.mux.Lock()
+ if connID == "" {
+ for _, conn := range u.conns {
+ (*conn).socket.WriteJSON(message)
+ }
+ } else {
+ if conn, ok := u.conns[connID]; ok {
+ (*conn).socket.WriteJSON(message)
+ }
+ }
+ u.mux.Unlock()
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+// Messaging Rooms ///////////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// ServerMessage sends a server message to the specified recipients in the Room. The parameter recipients can be nil or an empty slice
+// of string. In which case, the server message will be sent to all Users in the Room.
+func (r *Room) ServerMessage(message interface{}, messageType int, recipients []string) error {
+ if message == nil {
+ return errors.New("*Room.ServerMessage() requires a message")
+ }
+
+ if serverMessageCallbackSet {
+ serverMessageCallback(r, messageType, message)
+ }
+
+ return r.sendMessage(MessageTypeServer, messageType, recipients, "", message)
+}
+
+// ChatMessage sends a chat message to all Users in the Room.
+func (r *Room) ChatMessage(author string, message interface{}) error {
+ //REJECT INCORRECT INPUT
+ if len(author) == 0 {
+ return errors.New("*Room.ChatMessage() requires an author")
+ } else if message == nil {
+ return errors.New("*Room.ChatMessage() requires a message")
+ }
+
+ if chatMessageCallbackSet {
+ chatMessageCallback(author, r, message)
+ }
+
+ return r.sendMessage(MessageTypeChat, 0, nil, author, message)
+}
+
+// DataMessage sends a data message to the specified recipients in the Room. The parameter recipients can be nil or an empty slice
+// of string. In which case, the data message will be sent to all Users in the Room.
+func (r *Room) DataMessage(message interface{}, recipients []string) error {
+ //GET USER MAP
+ userMap, err := r.GetUserMap()
+ if err != nil {
+ return err
+ }
+
+ //CONSTRUCT MESSAGE
+ theMessage := map[string]interface{}{
+ helpers.ServerActionDataMessage: message,
+ }
+
+ //SEND MESSAGE TO USERS
+ if recipients == nil || len(recipients) == 0 {
+ for _, u := range userMap {
+ u.mux.Lock()
+ for _, conn := range u.conns {
+ conn.socket.WriteJSON(theMessage)
+ }
+ u.mux.Unlock()
+ }
+ } else {
+ for i := 0; i < len(recipients); i++ {
+ if u, ok := userMap[recipients[i]]; ok {
+ u.mux.Lock()
+ for _, conn := range u.conns {
+ conn.socket.WriteJSON(theMessage)
+ }
+ u.mux.Unlock()
+ }
+ }
+ }
+
+ //
+ return nil
+}
+
+func (r *Room) sendMessage(mt int, st int, rec []string, a string, m interface{}) error {
+ //GET USER MAP
+ userMap, err := r.GetUserMap()
+ if err != nil {
+ return err
+ }
+
+ //CONSTRUCT MESSAGE
+ message := map[string]map[string]interface{}{
+ helpers.ServerActionRoomMessage: make(map[string]interface{}),
+ }
+ // Server messages come with a sub-type
+ if mt == MessageTypeServer {
+ message[helpers.ServerActionRoomMessage]["s"] = st
+ }
+ // Non-server messages have authors
+ if len(a) > 0 && mt != MessageTypeServer {
+ message[helpers.ServerActionRoomMessage]["a"] = a
+ }
+ // The message
+ message[helpers.ServerActionRoomMessage]["m"] = m
+
+ //SEND MESSAGE TO USERS
+ if rec == nil || len(rec) == 0 {
+ for _, u := range userMap {
+ u.mux.Lock()
+ for _, conn := range u.conns {
+ conn.socket.WriteJSON(message)
+ }
+ u.mux.Unlock()
+ }
+ } else {
+ for i := 0; i < len(rec); i++ {
+ if u, ok := userMap[rec[i]]; ok {
+ u.mux.Lock()
+ for _, conn := range u.conns {
+ conn.socket.WriteJSON(message)
+ }
+ u.mux.Unlock()
+ }
+ }
+ }
+
+ return nil
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// VOICE STREAMS //////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// VoiceStream sends a voice stream from the client API to all the users in the room besides the user who is speaking.
+func (r *Room) VoiceStream(userName string, userSocket *websocket.Conn, stream interface{}) {
+ //GET USER MAP
+ userMap, err := r.GetUserMap()
+ if err != nil {
+ return
+ }
+
+ //CONSTRUCT VOICE MESSAGE
+ theMessage := map[string]map[string]interface{}{
+ helpers.ServerActionVoiceStream: {
+ "u": userName,
+ "d": stream,
+ },
+ }
+
+ //REMOVE SENDING USER FROM userMap
+ delete(userMap, userName) // COMMENT OUT FOR ECHO TESTS
+
+ //SEND MESSAGE TO USERS
+ for _, u := range userMap {
+ for _, conn := range u.conns {
+ (*conn).socket.WriteJSON(theMessage)
+ }
+ }
+
+ //CONSTRUCT PING MESSAGE
+ pingMessage := map[string]interface{}{
+ helpers.ServerActionVoicePing: nil,
+ }
+
+ //SEND PING MESSAGE TO SENDING USER
+ userSocket.WriteJSON(pingMessage)
+
+ //
+ return
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Callback Setters ///////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// SetPrivateMessageCallback sets the callback function for when a *User sends a private message to another *User.
+// The function passed must have the same parameter types as the following example:
+//
+// func onPrivateMessage(from *core.User, to *core.User, message interface{}) {
+// //code...
+// }
+func SetPrivateMessageCallback(cb func(*User, *User, interface{})) {
+ if !serverStarted {
+ privateMessageCallback = cb
+ privateMessageCallbackSet = true
+ }
+
+}
+
+// SetChatMessageCallback sets the callback function for when a *User sends a chat message to a *Room.
+// The function passed must have the same parameter types as the following example:
+//
+// func onChatMessage(userName string, room *core.Room, message interface{}) {
+// //code...
+// }
+func SetChatMessageCallback(cb func(string, *Room, interface{})) {
+ if !serverStarted {
+ chatMessageCallback = cb
+ chatMessageCallbackSet = true
+ }
+}
+
+// SetServerMessageCallback sets the callback function for when the server sends a message to a *Room.
+// The function passed must have the same parameter types as the following example:
+//
+// func onServerMessage(room *core.Room, messageType int, message interface{}) {
+// //code...
+// }
+//
+// The messageType value can be one of: core.ServerMessageGame, core.ServerMessageNotice,
+// core.ServerMessageImportant, or a custom value you have set.
+func SetServerMessageCallback(cb func(*Room, int, interface{})) {
+ if !serverStarted {
+ serverMessageCallback = cb
+ serverMessageCallbackSet = true
+ }
+}
diff --git a/rooms/roomTypes.go b/core/roomTypes.go
similarity index 92%
rename from rooms/roomTypes.go
rename to core/roomTypes.go
index f78e4f7..5a3572f 100644
--- a/rooms/roomTypes.go
+++ b/core/roomTypes.go
@@ -1,6 +1,4 @@
-package rooms
-
-import ()
+package core
var (
roomTypes = make(map[string]*RoomType)
@@ -17,10 +15,10 @@ type RoomType struct {
broadcastUserEnter bool
broadcastUserLeave bool
- createCallback func(*Room) // roomCreated
- deleteCallback func(*Room) // roomDeleted
- userEnterCallback func(*Room, string) // roomFrom, userName
- userLeaveCallback func(*Room, string) // roomFrom, userName
+ createCallback func(*Room) // roomCreated
+ deleteCallback func(*Room) // roomDeleted
+ userEnterCallback func(*Room, *RoomUser) // roomFrom, user
+ userLeaveCallback func(*Room, *RoomUser) // roomFrom, user
}
// NewRoomType Adds a RoomType to the server. A RoomType is used in conjunction with it's corresponding callbacks
@@ -131,7 +129,7 @@ func (r *RoomType) SetDeleteCallback(callback func(*Room)) *RoomType {
// and the string is the name of the User that entered.
//
// Note: You must call this BEFORE starting the server in order for it to take effect.
-func (r *RoomType) SetUserEnterCallback(callback func(*Room, string)) *RoomType {
+func (r *RoomType) SetUserEnterCallback(callback func(*Room, *RoomUser)) *RoomType {
if serverStarted {
return r
}
@@ -144,7 +142,7 @@ func (r *RoomType) SetUserEnterCallback(callback func(*Room, string)) *RoomType
// and the string is the name of the User that left.
//
// Note: You must call this BEFORE starting the server in order for it to take effect.
-func (r *RoomType) SetUserLeaveCallback(callback func(*Room, string)) *RoomType {
+func (r *RoomType) SetUserLeaveCallback(callback func(*Room, *RoomUser)) *RoomType {
if serverStarted {
return r
}
@@ -153,7 +151,7 @@ func (r *RoomType) SetUserLeaveCallback(callback func(*Room, string)) *RoomType
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
-// RoomType ATTRIBUTE READERS ////////////////////////////////////////////////////////////////////
+// RoomType ATTRIBUTE & CALLBACK READERS /////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
// ServerOnly returns true if the RoomType can only be manipulated by the server.
@@ -197,7 +195,7 @@ func (r *RoomType) HasDeleteCallback() bool {
}
// UserEnterCallback returns the function that this RoomType calls when a User enters a Room of this RoomType.
-func (r *RoomType) UserEnterCallback() func(*Room, string) {
+func (r *RoomType) UserEnterCallback() func(*Room, *RoomUser) {
return r.userEnterCallback
}
@@ -207,7 +205,7 @@ func (r *RoomType) HasUserEnterCallback() bool {
}
// UserLeaveCallback returns the function that this RoomType calls when a User leaves a Room of this RoomType.
-func (r *RoomType) UserLeaveCallback() func(*Room, string) {
+func (r *RoomType) UserLeaveCallback() func(*Room, *RoomUser) {
return r.userLeaveCallback
}
diff --git a/rooms/rooms.go b/core/rooms.go
similarity index 54%
rename from rooms/rooms.go
rename to core/rooms.go
index f4cabe1..df933e2 100644
--- a/rooms/rooms.go
+++ b/core/rooms.go
@@ -1,32 +1,16 @@
-// Package rooms contains all the necessary tools to make and work with Rooms. A Room represents
-// a place on the server where a User can join other Users.
-//
-// A Room can either be public or private. Private Rooms must be assigned an "owner", which is the name of a User, or the ServerName
-// from ServerSettings. The server's name that will be used for ownership of private Rooms can be set with the ServerSettings
-// option ServerName when starting the server. Though keep in mind, setting the ServerName in ServerSettings will prevent a User who wants to go by that name
-// from logging in. Public Rooms will accept a join request from any User, and private Rooms will only
-// accept a join request from someone who is on it's invite list. Only the owner of the Room or the server itself can invite
-// Users to a private Room. But remember, just because a User owns a private room doesn't mean the server cannot also invite
-// to the room via *Room.AddInvite() function.
-//
-// Rooms have their own variables which can be accessed and changed anytime. A Room variable can
-// be anything compatible with interface{}, so pretty much anything. Room variables should mainly be used
-// for things about the room itself that don't change very often (or, for instance, are absolutely needed for a joining
-// User).
-package rooms
+package core
import (
"errors"
- "github.com/gorilla/websocket"
"github.com/hewiefreeman/GopherGameServer/helpers"
"sync"
)
-// Room represents a room on the server that Users can join and leave. Use rooms.New() to make a new Room.
+// Room represents a room on the server that Users can join and leave. Use core.NewRoom() to make a new Room.
//
-// WARNING: When you use a *User object in your code, DO NOT dereference it. Instead, there are
-// many methods for *User for retrieving any information about them you could possibly need.
-// Dereferencing them could cause data races in the User fields that get locked by mutexes.
+// WARNING: When you use a *Room object in your code, DO NOT dereference it. Instead, there are
+// many methods for *Room for maniupulating and retrieving any information about them you could possibly need.
+// Dereferencing them could cause data races in the Room fields that get locked by mutexes.
type Room struct {
name string
rType string
@@ -41,45 +25,24 @@ type Room struct {
vars map[string]interface{}
}
-// RoomUser is the representation of a User in a Room. Note: These
-// are not the Users themselves. There are RoomUser methods that provide the same
-// functionality as the matching User methods, but if you really need to get a User type from one of these, use
-// users.Get() with the RoomUser's Name() function.
+// RoomUser represents a User inside of a Room. Use the *RoomUser.User() function to get a *User from a *RoomUser
type RoomUser struct {
- name string
- isGuest bool
- dbID int
-
- //mux LOCKS ALL FIELDS BELOW
- mux *sync.Mutex // Pointer to the User's mux
- conns map[string]*roomUserConn // User's conns info
+ user *User
-}
-
-type roomUserConn struct {
- socket *websocket.Conn
- roomIn **Room // Pointer to the User's Room pointer
- vars *map[string]interface{} // Pointer to the User's variables
+ mux sync.Mutex
+ conns map[string]*userConn
}
var (
- //THE Rooms AND Rooms MUTEX
rooms map[string]*Room = make(map[string]*Room)
- roomsMux sync.Mutex //LOCKS rooms
-
- //SERVER SETTINGS
- serverStarted bool = false
- serverPaused bool = false
- serverName string
- deleteRoomOnLeave bool = true
- multiConnect bool = false
+ roomsMux sync.Mutex
)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MAKE A NEW ROOM ////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// New adds a new room to the server. This can be called before or after starting the server.
+// NewRoom adds a new room to the server. This can be called before or after starting the server.
// Parameters:
//
// - name (string): Name of the Room
@@ -91,10 +54,10 @@ var (
// - maxUsers (int): Maximum User capacity (Note: 0 means no limit)
//
// - owner (string): The owner of the room. If provided a blank string, will set the owner to the ServerName from ServerSettings
-func New(name string, rType string, isPrivate bool, maxUsers int, owner string) (*Room, error) {
+func NewRoom(name string, rType string, isPrivate bool, maxUsers int, owner string) (*Room, error) {
//REJECT INCORRECT INPUT
if len(name) == 0 {
- return &Room{}, errors.New("rooms.New() requires a name")
+ return &Room{}, errors.New("core.NewRoom() requires a name")
} else if maxUsers < 0 {
maxUsers = 0
} else if owner == "" {
@@ -113,21 +76,17 @@ func New(name string, rType string, isPrivate bool, maxUsers int, owner string)
roomsMux.Unlock()
return &Room{}, errors.New("A Room with the name '" + name + "' already exists")
}
- userMap := make(map[string]*RoomUser)
- roomVars := make(map[string]interface{})
- invList := []string{}
- theRoom := Room{name: name, private: isPrivate, inviteList: invList, usersMap: userMap, maxUsers: maxUsers, vars: roomVars,
- owner: owner, rType: rType}
+ theRoom := Room{name: name, private: isPrivate, inviteList: []string{}, usersMap: make(map[string]*RoomUser), maxUsers: maxUsers,
+ vars: make(map[string]interface{}), owner: owner, rType: rType}
rooms[name] = &theRoom
- room := rooms[name]
roomsMux.Unlock()
//CALLBACK
if roomType.HasCreateCallback() {
- roomType.CreateCallback()(room)
+ roomType.CreateCallback()(&theRoom)
}
- return room, nil
+ return &theRoom, nil
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -144,17 +103,19 @@ func (r *Room) Delete() error {
}
// MAKE LEAVE MESSAGE
- leaveMessage := helpers.MakeClientResponse(helpers.ClientActionLeaveRoom, nil, helpers.NewError("", 0))
+ leaveMessage := helpers.MakeClientResponse(helpers.ClientActionLeaveRoom, nil, helpers.NoError())
// GO THROUGH ALL Users IN ROOM
- for _, v := range r.usersMap {
+ for _, u := range r.usersMap {
//CHANGE User's room POINTER TO nil & SEND MESSAGES
- v.mux.Lock()
- for key := range v.conns {
- (*v.conns[key]).socket.WriteJSON(leaveMessage)
- *((*v.conns[key]).roomIn) = nil
+ u.mux.Lock()
+ for key := range u.conns {
+ (*u.conns[key]).socket.WriteJSON(leaveMessage)
+ u.user.mux.Lock()
+ (*u.conns[key]).room = nil
+ u.user.mux.Unlock()
}
- v.mux.Unlock()
+ u.mux.Unlock()
}
r.usersMap = nil
@@ -179,11 +140,11 @@ func (r *Room) Delete() error {
// GET A ROOM /////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// Get finds a Room on the server. If the room does not exit, an error will be returned.
-func Get(roomName string) (*Room, error) {
+// GetRoom finds a Room on the server. If the room does not exit, an error will be returned.
+func GetRoom(roomName string) (*Room, error) {
//REJECT INCORRECT INPUT
if len(roomName) == 0 {
- return &Room{}, errors.New("rooms.Get() requires a room name")
+ return &Room{}, errors.New("core.GetRoom() requires a room name")
}
var room *Room
@@ -204,23 +165,19 @@ func Get(roomName string) (*Room, error) {
// ADD A USER /////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// AddUser adds a User to the Room.
-//
-// WARNING: This is only meant for internal Gopher Game Server mechanics. If you want to make a User join a Room, use
-// *User.Join() instead. Using this improperly will break server mechanics and cause errors and/or memory leaks.
-func (r *Room) AddUser(userName string, dbID int, isGuest bool, socket *websocket.Conn, roomIn **Room, userVars *map[string]interface{},
- userStatus *int, userMux *sync.Mutex, connID string) error {
+// AddUser adds a User to the Room. If you are using MultiConnect in ServerSettings, the connID
+// parameter is the connection ID associated with one of the connections attached to that User. This must
+// be provided when adding a User to a Room with MultiConnect enabled. Otherwise, an empty string can be used.
+func (r *Room) AddUser(user *User, connID string) error {
+ userName := user.Name()
// REJECT INCORRECT INPUT
- if len(userName) == 0 {
- return errors.New("*Room.AddUser() requires a user name")
- } else if socket == nil {
- return errors.New("*Room.AddUser() requires a user socket")
+ if user == nil {
+ return errors.New("*Room.AddUser() requires a valid User")
} else if multiConnect && len(connID) == 0 {
return errors.New("Must provide a connID when MultiConnect is enabled")
} else if !multiConnect {
connID = "1"
}
-
r.mux.Lock()
if r.usersMap == nil {
r.mux.Unlock()
@@ -229,10 +186,9 @@ func (r *Room) AddUser(userName string, dbID int, isGuest bool, socket *websocke
r.mux.Unlock()
return errors.New("The room '" + r.name + "' is full")
}
-
// CHECK IF THE ROOM IS PRIVATE, OWNER JOINS FREELY
if r.private && userName != r.owner {
- // IF SO, CHECK IF THIS USER IS ON THE INVITE LIST
+ // IF PRIVATE AND NOT OWNER, CHECK IF THIS USER IS ON THE INVITE LIST
if len(r.inviteList) > 0 {
for i := 0; i < len(r.inviteList); i++ {
if (r.inviteList)[i] == userName {
@@ -251,64 +207,73 @@ func (r *Room) AddUser(userName string, dbID int, isGuest bool, socket *websocke
}
// CHECK IF USER IS ALREADY IN THE ROOM
- var roomUserExists bool = false
- if rUser, ok := r.usersMap[userName]; ok {
- roomUserExists = true
+ var ru *RoomUser
+ var ok bool
+ if ru, ok = r.usersMap[userName]; ok {
if !multiConnect {
r.mux.Unlock()
return errors.New("User '" + userName + "' is already in room '" + r.name + "'")
}
- (*rUser.mux).Lock()
- if _, ok := rUser.conns[connID]; ok {
+ ru.mux.Lock()
+ if _, ok := ru.conns[connID]; ok {
r.mux.Unlock()
- (*rUser.mux).Unlock()
+ ru.mux.Unlock()
return errors.New("User '" + userName + "' is already in room '" + r.name + "'")
}
- (*rUser.mux).Unlock()
+ ru.mux.Unlock()
}
- userList := r.usersMap
- newConn := roomUserConn{socket: socket, roomIn: roomIn, vars: userVars}
// ADD User TO ROOM
- if roomUserExists {
- (*r.usersMap[userName]).conns[connID] = &newConn
+ user.mux.Lock()
+ c := user.conns[connID]
+ if c == nil {
+ r.mux.Unlock()
+ return errors.New("Invalid connection ID")
+ }
+ if ru != nil {
+ (*r.usersMap[userName]).mux.Lock()
+ (*r.usersMap[userName]).conns[connID] = c
+ (*r.usersMap[userName]).mux.Unlock()
} else {
- conns := make(map[string]*roomUserConn)
- conns[connID] = &newConn
- newUser := RoomUser{name: userName, isGuest: isGuest, dbID: dbID, mux: userMux, conns: conns}
+ conns := make(map[string]*userConn)
+ conns[connID] = c
+ newUser := RoomUser{user: user, conns: conns}
r.usersMap[userName] = &newUser
+ ru = r.usersMap[userName]
}
- r.mux.Unlock()
-
// CHANGE USER'S ROOM
- (*userMux).Lock()
- *roomIn = r
- (*userMux).Unlock()
+ c.room = r
+
+ user.mux.Unlock()
+ r.mux.Unlock()
//
roomType := roomTypes[r.rType]
if roomType.BroadcastUserEnter() {
//BROADCAST ENTER TO USERS IN ROOM
- message := make(map[string]interface{})
- message[helpers.ServerActionUserEnter] = make(map[string]interface{})
- message[helpers.ServerActionUserEnter].(map[string]interface{})["u"] = userName
- message[helpers.ServerActionUserEnter].(map[string]interface{})["g"] = isGuest
- for _, u := range userList {
+ message := map[string]map[string]interface{}{
+ helpers.ServerActionUserEnter: {
+ "u": userName,
+ "g": user.isGuest,
+ },
+ }
+ for _, u := range r.usersMap {
u.mux.Lock()
- for _, conn := range u.conns {
- (*conn).socket.WriteJSON(message)
+ if u.user.Name() != userName {
+ for _, conn := range u.conns {
+ (*conn).socket.WriteJSON(message)
+ }
}
u.mux.Unlock()
}
}
-
// CALLBACK
if roomType.HasUserEnterCallback() {
- roomType.UserEnterCallback()(r, userName)
+ roomType.UserEnterCallback()(r, ru)
}
// SEND RESPONSE TO CLIENT
- clientResp := helpers.MakeClientResponse(helpers.ClientActionJoinRoom, r.Name(), helpers.NewError("", 0))
- socket.WriteJSON(clientResp)
+ clientResp := helpers.MakeClientResponse(helpers.ClientActionJoinRoom, r.Name(), helpers.NoError())
+ c.socket.WriteJSON(clientResp)
//
return nil
@@ -321,10 +286,10 @@ func (r *Room) AddUser(userName string, dbID int, isGuest bool, socket *websocke
// RemoveUser removes a User from the room. If you are using MultiConnect in ServerSettings, the connID
// parameter is the connection ID associated with one of the connections attached to that User. This must
// be provided when removing a User from a Room with MultiConnect enabled. Otherwise, an empty string can be used.
-func (r *Room) RemoveUser(userName string, connID string) error {
+func (r *Room) RemoveUser(user *User, connID string) error {
//REJECT INCORRECT INPUT
- if len(userName) == 0 {
- return errors.New("*Room.RemoveUser() requires a user name")
+ if user == nil || len(user.name) == 0 {
+ return errors.New("*Room.RemoveUser() requires a valid *User")
} else if multiConnect && len(connID) == 0 {
return errors.New("Must provide a connID when MultiConnect is enabled")
} else if !multiConnect {
@@ -336,36 +301,43 @@ func (r *Room) RemoveUser(userName string, connID string) error {
r.mux.Unlock()
return errors.New("The room '" + r.name + "' does not exist")
}
- if _, ok := r.usersMap[userName]; !ok {
+ var ok bool
+ var ru *RoomUser
+ if ru, ok = r.usersMap[user.name]; !ok {
r.mux.Unlock()
- return errors.New("User '" + userName + "' is not in room '" + r.name + "'")
+ return errors.New("User '" + user.name + "' is not in room '" + r.name + "'")
}
- if _, ok := (*r.usersMap[userName]).conns[connID]; !ok {
+ ru.mux.Lock()
+ var uConn *userConn
+ if uConn, ok = ru.conns[connID]; !ok {
r.mux.Unlock()
+ ru.mux.Unlock()
return errors.New("Invalid connID")
}
- uConn := *((*r.usersMap[userName]).conns[connID])
- uMux := (*r.usersMap[userName]).mux
- delete((*r.usersMap[userName]).conns, connID)
- if len((*r.usersMap[userName]).conns) == 0 {
- delete(r.usersMap, userName)
+ delete(ru.conns, connID)
+ // Remove user when no conns are left in room
+ if len(ru.conns) == 0 {
+ delete(r.usersMap, user.name)
}
+ ru.mux.Unlock()
userList := r.usersMap
r.mux.Unlock()
//
roomType := roomTypes[r.rType]
//DELETE THE ROOM IF THE OWNER LEFT AND UserRoomControl IS ENABLED
- if deleteRoomOnLeave && userName == r.owner {
+ if deleteRoomOnLeave && user.name == r.owner {
deleteErr := r.Delete()
if deleteErr != nil {
return deleteErr
}
} else if roomType.BroadcastUserLeave() {
//CONSTRUCT LEAVE MESSAGE
- message := make(map[string]interface{})
- message[helpers.ServerActionUserLeave] = make(map[string]interface{})
- message[helpers.ServerActionUserLeave].(map[string]interface{})["u"] = userName
+ message := map[string]map[string]interface{}{
+ helpers.ServerActionUserLeave: {
+ "u": user.name,
+ },
+ }
//SEND MESSAGE TO USERS
for _, u := range userList {
@@ -378,17 +350,17 @@ func (r *Room) RemoveUser(userName string, connID string) error {
}
// CHANGE USER'S ROOM
- uMux.Lock()
- *(uConn.roomIn) = nil
- uMux.Unlock()
+ user.mux.Lock()
+ uConn.room = nil
+ user.mux.Unlock()
//CALLBACK
if roomType.HasUserLeaveCallback() {
- roomType.UserLeaveCallback()(r, userName)
+ roomType.UserLeaveCallback()(r, ru)
}
//SEND RESPONSE TO CLIENT
- clientResp := helpers.MakeClientResponse(helpers.ClientActionLeaveRoom, nil, helpers.NewError("", 0))
+ clientResp := helpers.MakeClientResponse(helpers.ClientActionLeaveRoom, r.Name(), helpers.NoError())
uConn.socket.WriteJSON(clientResp)
//
@@ -402,10 +374,10 @@ func (r *Room) RemoveUser(userName string, connID string) error {
// AddInvite adds a User to a private Room's invite list. This is only meant for internal Gopher Game Server mechanics.
// If you want a User to invite someone to a private room, use the *User.Invite() function instead.
//
-// NOTE: You can use this function safely, but remember that private rooms are designed to have an "owner",
+// NOTE: Remember that private rooms are designed to have an "owner",
// and only the owner should be able to send an invite and revoke an invitation for their Rooms. Also, *User.Invite()
-// will send an invite message to the invited User that the client API can easily receive. Though if you wish to make
-// your own implementations for this, don't hesitate!
+// will send an invite notification message to the invited User that the client API can easily receive. Though if you wish to make
+// your own implementations for sending and receiving these notifications, this function is safe to use.
func (r *Room) AddInvite(userName string) error {
if !r.private {
return errors.New("Room is not private")
@@ -435,14 +407,12 @@ func (r *Room) AddInvite(userName string) error {
// REMOVE FROM inviteList /////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// RemoveInvite removes a User from a private Room's invite list.
+// RemoveInvite removes a User from a private Room's invite list. To make a User remove someone from their room themselves,
+// use the *User.RevokeInvite() function.
//
// NOTE: You can use this function safely, but remember that private rooms are designed to have an "owner",
// and only the owner should be able to send an invite and revoke an invitation for their Rooms. But if you find the
// need to break the rules here, by all means do so!
-//
-// WARNING: This is only meant for internal Gopher Game Server mechanics. If you want a User to remove someone from the room's
-// private invite list, use the *User.RevokeInvite() function instead.
func (r *Room) RemoveInvite(userName string) error {
if !r.private {
return errors.New("Room is not private")
@@ -451,13 +421,15 @@ func (r *Room) RemoveInvite(userName string) error {
}
r.mux.Lock()
+
if r.usersMap == nil {
r.mux.Unlock()
return errors.New("The room '" + r.name + "' does not exist")
}
for i := 0; i < len(r.inviteList); i++ {
if r.inviteList[i] == userName {
- r.inviteList = append(r.inviteList[:i], r.inviteList[i+1:]...)
+ r.inviteList[i] = r.inviteList[len(r.inviteList)-1]
+ r.inviteList = r.inviteList[:len(r.inviteList)-1]
break
}
if i == len(r.inviteList)-1 {
@@ -471,43 +443,6 @@ func (r *Room) RemoveInvite(userName string) error {
return nil
}
-///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// GET A ROOM's inviteList ////////////////////////////////////////////////////////////////////////////////////
-///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-// InviteList gets a private Room's invite list.
-func (r *Room) InviteList() ([]string, error) {
- r.mux.Lock()
- if r.usersMap == nil {
- r.mux.Unlock()
- return []string{}, errors.New("The room '" + r.name + "' does not exist")
- }
- list := r.inviteList
- r.mux.Unlock()
- //
- return list, nil
-}
-
-///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// GET A Room's usersMap //////////////////////////////////////////////////////////////////////////////////////
-///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-// GetUserMap retrieves all the RoomUsers as a map[string]*RoomUser.
-func (r *Room) GetUserMap() (map[string]*RoomUser, error) {
- var err error
- var userMap map[string]*RoomUser
-
- r.mux.Lock()
- if r.usersMap == nil {
- err = errors.New("The room '" + r.name + "' does not exist")
- } else {
- userMap = r.usersMap
- }
- r.mux.Unlock()
-
- return userMap, err
-}
-
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Room ATTRIBUTE READERS /////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -546,81 +481,62 @@ func (r *Room) NumUsers() int {
return len(m)
}
-///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// RoomUser ATTRIBUTE READERS /////////////////////////////////////////////////////////////////////////////////
-///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-// Name gets the name of the RoomUser.
-func (u *RoomUser) Name() string {
- return u.name
-}
-
-// IsGuest returns true if the RoomUser is a guest.
-func (u *RoomUser) IsGuest() bool {
- return u.isGuest
-}
-
-// DatabaseID gets the database index of the RoomUser.
-func (u *RoomUser) DatabaseID() int {
- return u.dbID
+// InviteList gets a private Room's invite list.
+func (r *Room) InviteList() ([]string, error) {
+ r.mux.Lock()
+ if r.usersMap == nil {
+ r.mux.Unlock()
+ return []string{}, errors.New("The room '" + r.name + "' does not exist")
+ }
+ list := r.inviteList
+ r.mux.Unlock()
+ //
+ return list, nil
}
-///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// SERVER STARTUP FUNCTIONS ///////////////////////////////////////////////////////////////////////////////////
-///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// GetUserMap retrieves all the RoomUsers as a map[string]*RoomUser.
+func (r *Room) GetUserMap() (map[string]*RoomUser, error) {
+ var err error
+ var userMap map[string]*RoomUser
-// SetServerStarted is for Gopher Game Server internal mechanics.
-func SetServerStarted(val bool) {
- if !serverStarted {
- serverStarted = val
+ r.mux.Lock()
+ if r.usersMap == nil {
+ err = errors.New("The room '" + r.name + "' does not exist")
+ } else {
+ userMap = r.usersMap
}
-}
+ r.mux.Unlock()
-// SettingsSet is for Gopher Game Server internal mechanics.
-func SettingsSet(name string, deleteOnLeave bool, multiConn bool) {
- if !serverStarted {
- serverName = name
- deleteRoomOnLeave = deleteOnLeave
- multiConnect = multiConn
- }
+ return userMap, err
}
-//////////////////////////////////////////////////////////////////////////////////////////////////////
-// SERVER PAUSE AND RESUME ///////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////////////////////////////////
-
-// Pause is only for internal Gopher Game Server mechanics.
-func Pause() {
- if !serverPaused {
- serverPaused = true
- serverStarted = false
- }
+// RoomCount returns the number of Rooms created on the server.
+func RoomCount() int {
+ roomsMux.Lock()
+ length := len(rooms)
+ roomsMux.Unlock()
+ return length
}
-// Resume is only for internal Gopher Game Server mechanics.
-func Resume() {
- if serverPaused {
- serverStarted = true
- serverPaused = false
- }
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// RoomUser ATTRIBUTE READERS /////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// User gets the *User object of a *RoomUser.
+func (u *RoomUser) User() *User {
+ return u.user
}
-// Resume is only for internal Gopher Game Server mechanics.
-func GetState() map[string]interface{} {
- state := make(map[string]interface{})
- roomsMux.Lock()
- for _, room := range rooms {
- room.mux.Lock()
- state[room.name] = make(map[string]interface{})
- state[room.name].(map[string]interface{})["t"] = room.rType
- state[room.name].(map[string]interface{})["p"] = room.private
- state[room.name].(map[string]interface{})["o"] = room.owner
- state[room.name].(map[string]interface{})["m"] = room.maxUsers
- state[room.name].(map[string]interface{})["i"] = room.inviteList
- state[room.name].(map[string]interface{})["v"] = room.vars
- room.mux.Unlock()
- }
- roomsMux.Unlock()
- //
- return state
+// ConnectionIDs returns a []string of all the RoomUser's connection IDs. With MultiConnect in ServerSettings enabled,
+// this will give you all the connections for this User that are currently in the Room. Otherwise, if you want
+// all the User's connection IDs (not just the connections in the specified Room), use *User.ConnectionIDs() after getting
+// the *User object with the *RoomUser.User() function.
+func (u *RoomUser) ConnectionIDs() []string {
+ u.mux.Lock()
+ ids := make([]string, 0, len(u.conns))
+ for id := range u.conns {
+ ids = append(ids, id)
+ }
+ u.mux.Unlock()
+ return ids
}
diff --git a/users/users.go b/core/users.go
similarity index 65%
rename from users/users.go
rename to core/users.go
index 846eeea..11114f8 100644
--- a/users/users.go
+++ b/core/users.go
@@ -1,15 +1,10 @@
-// Package users contains all the necessary tools to make and working with Users. A User is a client
-// who has successfully logged into the server. You can think of clients who are not attached to a User
-// as, for instance, someone in the login screen, but are still connected to the server. A client doesn't
-// have to be a User to be able to call your CustomClientActions, so keep that in mind when making them!
-package users
+package core
import (
"errors"
"github.com/gorilla/websocket"
"github.com/hewiefreeman/GopherGameServer/database"
"github.com/hewiefreeman/GopherGameServer/helpers"
- "github.com/hewiefreeman/GopherGameServer/rooms"
"sync"
)
@@ -30,7 +25,7 @@ type User struct {
databaseID int
isGuest bool
- //mux LOCKS ALL FIELDS BELOW
+ //mux lock all items below
mux sync.Mutex
status int
friends map[string]*database.Friend
@@ -38,31 +33,25 @@ type User struct {
}
type userConn struct {
- //MUST LOCK clientMux WHEN GETTING/SETTING *user
+ // Must lock *clientMux when using *user
clientMux *sync.Mutex
user **User
socket *websocket.Conn
- room *rooms.Room
- vars map[string]interface{}
+
+ //Must lock user's mux to use below items
+ room *Room
+ vars map[string]interface{}
}
var (
users map[string]*User = make(map[string]*User)
usersMux sync.Mutex
- serverStarted bool = false
- serverPaused bool = false
-
- serverName string
- kickOnLogin bool = false
- sqlFeatures bool = false
- rememberMe bool = false
- multiConnect bool = false
-
-
- LoginCallback func(string, int, map[string]interface{}, map[string]interface{}) bool // LoginCallback is only for internal Gopher Game Server mechanics.
- LogoutCallback func(string, int) // LogoutCallback is only for internal Gopher Game Server mechanics.
+ // LoginCallback is only for internal Gopher Game Server mechanics.
+ LoginCallback func(string, int, map[string]interface{}, map[string]interface{}) bool
+ // LogoutCallback is only for internal Gopher Game Server mechanics.
+ LogoutCallback func(string, int)
)
// These represent the four statuses a User could be.
@@ -92,7 +81,7 @@ const (
// Login logs a User in to the service.
func Login(userName string, dbID int, autologPass string, isGuest bool, remMe bool, socket *websocket.Conn,
connUser **User, clientMux *sync.Mutex) (string, helpers.GopherError) {
- //REJECT INCORRECT INPUT
+ // Verify input
if serverPaused {
return "", helpers.NewError(errorServerPaused, helpers.ErrorServerPaused)
} else if len(userName) == 0 {
@@ -105,18 +94,18 @@ func Login(userName string, dbID int, autologPass string, isGuest bool, remMe bo
return "", helpers.NewError(errorRequiredSocket, helpers.ErrorAuthRequiredSocket)
}
- //ALWAYS SET A GUEST'S id TO -1
+ // Guests always have -1 databaseID
databaseID := dbID
if isGuest {
databaseID = -1
}
- //RUN CALLBACK
+ // Callback
if LoginCallback != nil && !LoginCallback(userName, dbID, nil, nil) {
return "", helpers.NewError(errorDenied, helpers.ErrorActionDenied)
}
- //MAKE *User IN users MAP & MAKE connID
+ // Make *User in users & make connID
var connID string
var connErr error
var userExists bool = false
@@ -126,32 +115,32 @@ func Login(userName string, dbID int, autologPass string, isGuest bool, remMe bo
if userOnline, ok := users[userName]; ok {
userExists = true
if kickOnLogin {
- //REMOVE USER FROM THEIR CURRENT ROOM IF ANY
+ // Kick user & remove from room
userOnline.mux.Lock()
for connKey, conn := range userOnline.conns {
userRoom := (*conn).room
if userRoom != nil && userRoom.Name() != "" {
userOnline.mux.Unlock()
- userRoom.RemoveUser(userOnline.name, connKey)
+ userRoom.RemoveUser(userOnline, connKey)
userOnline.mux.Lock()
}
(*(*conn).clientMux).Lock()
*((*conn).user) = nil
(*(*conn).clientMux).Unlock()
- //SEND LOGOUT MESSAGE TO CLIENT
- clientResp := helpers.MakeClientResponse(helpers.ClientActionLogout, nil, helpers.NewError("", 0))
+ // Send logout message to client
+ clientResp := helpers.MakeClientResponse(helpers.ClientActionLogout, nil, helpers.NoError())
(*conn).socket.WriteJSON(clientResp)
}
userOnline.mux.Unlock()
- //DELETE FROM users MAP
+ // Remove user from users map
delete(users, userName)
- //MAKE connID
+ // Make connID
connID = "1"
userExists = false
} else if multiConnect {
- //MAKE UNIQUE connID
+ // Make a unique connID
for {
connID, connErr = helpers.GenerateSecureString(5)
if connErr != nil {
@@ -170,71 +159,47 @@ func Login(userName string, dbID int, autologPass string, isGuest bool, remMe bo
return "", helpers.NewError(errorAlreadyLogged, helpers.ErrorAuthAlreadyLogged)
}
} else if multiConnect {
- //MAKE UNIQUE connID
+ // Make connID
connID, connErr = helpers.GenerateSecureString(5)
if connErr != nil {
usersMux.Unlock()
return "", helpers.NewError(errorUnexpected, helpers.ErrorAuthUnexpected)
}
} else {
- //MAKE connID
+ // Make connID
connID = "1"
}
- //MAKE THE userConn
+ // Make the userConn
vars := make(map[string]interface{})
conn := userConn{socket: socket, room: nil, vars: vars, user: connUser, clientMux: clientMux}
- //FRIENDS OBJECTS
- var friends []map[string]interface{} = []map[string]interface{}{}
+ // Make friends objects
+ var u *User
+ var friends []map[string]interface{}
var friendsMap map[string]*database.Friend
- //ADD THE userConn TO THE User OR MAKE THE User
+ // Add the userConn to the User or make new User
if userExists {
(*users[userName]).mux.Lock()
(*users[userName]).conns[connID] = &conn
friendsMap = (*users[userName]).friends
(*users[userName]).mux.Unlock()
- //MAKE FRINDS LIST FOR SERVER RESPONSE
- for _, val := range friendsMap {
- friendEntry := make(map[string]interface{})
- friendEntry["n"] = val.Name()
- friendEntry["rs"] = val.RequestStatus()
- if val.RequestStatus() == database.FriendStatusAccepted {
- //GET THE User STATUS
- if friend, ok := users[val.Name()]; ok {
- friendEntry["s"] = friend.Status()
- } else {
- friendEntry["s"] = StatusOffline
- }
- }
- friends = append(friends, friendEntry)
- }
+ // Make friends list for response
+ friends = makeFriendsResponse(friendsMap)
} else {
- //GET User's Friend LIST FROM DATABASE
+ // Get friend list from database
if dbID != -1 && sqlFeatures {
var friendsErr error
- friendsMap, friendsErr = database.GetFriends(dbID) // map[string]Friend
- if friendsErr == nil {
- //MAKE FRINDS LIST FOR SERVER RESPONSE
- for _, val := range friendsMap {
- friendEntry := make(map[string]interface{})
- friendEntry["n"] = val.Name()
- friendEntry["rs"] = val.RequestStatus()
- if val.RequestStatus() == database.FriendStatusAccepted {
- //GET THE User STATUS
- if friend, ok := users[val.Name()]; ok {
- friendEntry["s"] = friend.Status()
- } else {
- friendEntry["s"] = StatusOffline
- }
- }
- friends = append(friends, friendEntry)
- }
+ if friendsMap, friendsErr = database.GetFriends(dbID); friendsErr == nil {
+ // Make friends list for response
+ friends = makeFriendsResponse(friendsMap)
}
}
- conns := make(map[string]*userConn)
- conns[connID] = &conn
+ conns := map[string]*userConn{
+ connID: &conn,
+ }
newUser := User{name: userName, databaseID: databaseID, isGuest: isGuest, status: 0,
friends: friendsMap, conns: conns}
- users[userName] = &newUser
+ u = &newUser
+ users[userName] = u
}
(*conn.clientMux).Lock()
*(conn.user) = users[userName]
@@ -242,37 +207,58 @@ func Login(userName string, dbID int, autologPass string, isGuest bool, remMe bo
//
usersMux.Unlock()
- //SEND ONLINE MESSAGE TO FRIENDS
- statusMessage := make(map[string]interface{})
- statusMessage[helpers.ServerActionFriendStatusChange] = make(map[string]interface{})
- statusMessage[helpers.ServerActionFriendStatusChange].(map[string]interface{})["n"] = userName
- statusMessage[helpers.ServerActionFriendStatusChange].(map[string]interface{})["s"] = 0
- for key, val := range friendsMap {
- if val.RequestStatus() == database.FriendStatusAccepted {
- friend, friendErr := Get(key)
- if friendErr == nil {
- friend.mux.Lock()
- for _, friendConn := range friend.conns {
- friendConn.socket.WriteJSON(statusMessage)
- }
- friend.mux.Unlock()
- }
- }
+ // Send online message to friends
+ statusMessage := map[string]map[string]interface{}{
+ helpers.ServerActionFriendStatusChange: {
+ "n": userName,
+ "s": 0,
+ },
}
+ u.sendToFriends(statusMessage)
- //SUCCESS, SEND RESPONSE TO CLIENT
- responseVal := make(map[string]interface{})
- responseVal["n"] = userName
- responseVal["f"] = friends
+ // Login success, send response to client
+ var responseVal map[string]interface{}
if rememberMe && len(autologPass) > 0 && remMe {
- responseVal["ai"] = dbID
- responseVal["ap"] = autologPass
+ responseVal = map[string]interface{}{
+ "n": userName,
+ "f": friends,
+ "ai": dbID,
+ "ap": autologPass,
+ }
+ } else {
+ responseVal = map[string]interface{}{
+ "n": userName,
+ "f": friends,
+ }
}
- clientResp := helpers.MakeClientResponse(helpers.ClientActionLogin, responseVal, helpers.NewError("", 0))
+ clientResp := helpers.MakeClientResponse(helpers.ClientActionLogin, responseVal, helpers.NoError())
socket.WriteJSON(clientResp)
//
- return connID, helpers.NewError("", 0)
+ return connID, helpers.NoError()
+}
+
+func makeFriendsResponse(friendsMap map[string]*database.Friend) []map[string]interface{} {
+ friends := make([]map[string]interface{}, len(friendsMap), len(friendsMap))
+ i := 0;
+ for _, val := range friendsMap {
+ frs := val.RequestStatus()
+ friendEntry := map[string]interface{}{
+ "n": val.Name(),
+ "rs": frs,
+ }
+ if frs == database.FriendStatusAccepted {
+ // Get the friend's status
+ if friend, ok := users[val.Name()]; ok {
+ friendEntry["s"] = friend.Status()
+ } else {
+ friendEntry["s"] = StatusOffline
+ }
+ }
+ friends[i] = friendEntry
+ i++;
+ }
+ return friends
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -289,18 +275,18 @@ func AutoLogIn(tag string, pass string, newPass string, dbID int, conn *websocke
return "", helpers.NewError(errorServerPaused, helpers.ErrorServerPaused)
}
- //VERIFY AND GET USER NAME FROM DATABASE
+ // Verify and get user name from database
userName, autoLogErr := database.AutoLoginClient(tag, pass, newPass, dbID)
if autoLogErr.ID != 0 {
return "", autoLogErr
}
- //
+ // Log user in
connID, userErr := Login(userName, dbID, newPass, false, true, conn, connUser, clientMux)
if userErr.ID != 0 {
return "", userErr
}
- //
- return connID, helpers.NewError("", 0)
+
+ return connID, helpers.NoError()
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -317,7 +303,7 @@ func (u *User) Logout(connID string) {
connID = "1"
}
- //REMOVE USER FROM THEIR ROOM
+ // Remove user from their room
u.mux.Lock()
if _, ok := u.conns[connID]; !ok {
u.mux.Unlock()
@@ -326,30 +312,21 @@ func (u *User) Logout(connID string) {
currRoom := (*u.conns[connID]).room
if currRoom != nil && currRoom.Name() != "" {
u.mux.Unlock()
- currRoom.RemoveUser(u.name, connID)
+ currRoom.RemoveUser(u, connID)
u.mux.Lock()
}
if len(u.conns) == 1 {
- //SEND STATUS CHANGE TO FRIENDS
- statusMessage := make(map[string]interface{})
- statusMessage[helpers.ServerActionFriendStatusChange] = make(map[string]interface{})
- statusMessage[helpers.ServerActionFriendStatusChange].(map[string]interface{})["n"] = u.name
- statusMessage[helpers.ServerActionFriendStatusChange].(map[string]interface{})["s"] = StatusOffline
- for key, val := range u.friends {
- if val.RequestStatus() == database.FriendStatusAccepted {
- friend, friendErr := Get(key)
- if friendErr == nil {
- friend.mux.Lock()
- for _, friendConn := range friend.conns {
- (*friendConn).socket.WriteJSON(statusMessage)
- }
- friend.mux.Unlock()
- }
- }
+ // Send status change to friends
+ statusMessage := map[string]map[string]interface{}{
+ helpers.ServerActionFriendStatusChange: {
+ "n": u.name,
+ "s": StatusOffline,
+ },
}
+ u.sendToFriends(statusMessage)
}
- //LOG USER OUT
+ // Log user out
(*u.conns[connID]).clientMux.Lock()
if *((*u.conns[connID]).user) != nil {
*((*u.conns[connID]).user) = nil
@@ -358,7 +335,7 @@ func (u *User) Logout(connID string) {
socket := (*u.conns[connID]).socket
delete(u.conns, connID)
if len(u.conns) == 0 {
- // DELETE THE USER IF THERE ARE NO MORE CONNS
+ // Delete user if there are no more conns
u.mux.Unlock()
usersMux.Lock()
delete(users, u.name)
@@ -367,11 +344,11 @@ func (u *User) Logout(connID string) {
u.mux.Unlock()
}
- //SEND RESPONSE TO CLIENT
- clientResp := helpers.MakeClientResponse(helpers.ClientActionLogout, nil, helpers.NewError("", 0))
+ // Send response
+ clientResp := helpers.MakeClientResponse(helpers.ClientActionLogout, nil, helpers.NoError())
socket.WriteJSON(clientResp)
- //RUN CALLBACK
+ // Run callback
if LogoutCallback != nil {
LogoutCallback(u.Name(), u.DatabaseID())
}
@@ -381,56 +358,47 @@ func (u *User) Logout(connID string) {
func (u *User) Kick() {
u.mux.Lock()
- //SEND STATUS CHANGE TO FRIENDS
- statusMessage := make(map[string]interface{})
- statusMessage[helpers.ServerActionFriendStatusChange] = make(map[string]interface{})
- statusMessage[helpers.ServerActionFriendStatusChange].(map[string]interface{})["n"] = u.name
- statusMessage[helpers.ServerActionFriendStatusChange].(map[string]interface{})["s"] = StatusOffline
- for key, val := range u.friends {
- if val.RequestStatus() == database.FriendStatusAccepted {
- friend, friendErr := Get(key)
- if friendErr == nil {
- friend.mux.Lock()
- for _, friendConn := range friend.conns {
- (*friendConn).socket.WriteJSON(statusMessage)
- }
- friend.mux.Unlock()
- }
- }
+ // Send status change message to friends
+ statusMessage := map[string]map[string]interface{}{
+ helpers.ServerActionFriendStatusChange: {
+ "n": u.name,
+ "s": StatusOffline,
+ },
}
+ u.sendToFriends(statusMessage)
- //MAKE CLIENT RESPONSE
- clientResp := helpers.MakeClientResponse(helpers.ClientActionLogout, nil, helpers.NewError("", 0))
+ // Make response
+ clientResp := helpers.MakeClientResponse(helpers.ClientActionLogout, nil, helpers.NoError())
- //LOOP THROUGH CONNECTIONS
+ // Go through all connections
for connID, conn := range u.conns {
- //REMOVE CONNECTION FROM THEIR ROOM
+ // Remove from room
currRoom := (*conn).room
if currRoom != nil && currRoom.Name() != "" {
u.mux.Unlock()
- currRoom.RemoveUser(u.name, connID)
+ currRoom.RemoveUser(u, connID)
u.mux.Lock()
}
- //LOG CONNECTION OUT
+ // Log connection out
(*conn).clientMux.Lock()
if *((*conn).user) != nil {
*((*conn).user) = nil
}
(*conn).clientMux.Unlock()
- //SEND RESPONSE
+ // Send response
(*conn).socket.WriteJSON(clientResp)
}
u.mux.Unlock()
- //REMOVE USER FROM users
+ // Remove from users
usersMux.Lock()
delete(users, u.name)
usersMux.Unlock()
- //RUN CALLBACK
+ // Run callback
if LogoutCallback != nil {
LogoutCallback(u.Name(), u.DatabaseID())
}
@@ -440,9 +408,9 @@ func (u *User) Kick() {
// GET A USER ////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
-// Get finds a logged in User by their name. Returns an error if the User is not online.
-func Get(userName string) (*User, error) {
- //REJECT INCORRECT INPUT
+// GetUser finds a logged in User by their name. Returns an error if the User is not online.
+func GetUser(userName string) (*User, error) {
+ // Verify input
if len(userName) == 0 {
return &User{}, errors.New("users.Get() requires a user name")
} else if serverPaused {
@@ -470,7 +438,7 @@ func Get(userName string) (*User, error) {
// Join makes a User join a Room. If you are using MultiConnect in ServerSettings, the connID
// parameter is the connection ID associated with one of the connections attached to that User. This must
// be provided when making a User join a Room with MultiConnect enabled. Otherwise, an empty string can be used.
-func (u *User) Join(r *rooms.Room, connID string) error {
+func (u *User) Join(r *Room, connID string) error {
if multiConnect && len(connID) == 0 {
return errors.New("Must provide a connID when MultiConnect is enabled")
} else if !multiConnect {
@@ -486,19 +454,15 @@ func (u *User) Join(r *rooms.Room, connID string) error {
u.mux.Unlock()
return errors.New("User '" + u.name + "' is already in room '" + r.Name() + "'")
} else if currRoom != nil && currRoom.Name() != "" {
- //LEAVE USER'S CURRENT ROOM
+ // Leave current room
u.mux.Unlock()
u.Leave(connID)
u.mux.Lock()
}
- roomPointer := &((*u.conns[connID]).room)
- varsPointer := &((*u.conns[connID]).vars)
- socketPointer := ((*u.conns[connID]).socket)
- statusPointer := &u.status
u.mux.Unlock()
- //ADD USER TO DESIGNATED ROOM
- addErr := r.AddUser(u.name, u.databaseID, u.isGuest, socketPointer, roomPointer, varsPointer, statusPointer, &u.mux, connID)
+ // Add user to room
+ addErr := r.AddUser(u, connID)
if addErr != nil {
return addErr
}
@@ -525,7 +489,7 @@ func (u *User) Leave(connID string) error {
currRoom := (*u.conns[connID]).room
u.mux.Unlock()
if currRoom != nil && currRoom.Name() != "" {
- removeErr := currRoom.RemoveUser(u.name, connID)
+ removeErr := currRoom.RemoveUser(u, connID)
if removeErr != nil {
return removeErr
}
@@ -543,29 +507,18 @@ func (u *User) Leave(connID string) error {
// SetStatus sets the status of a User. Also sends a notification to all the User's Friends (with the request
// status "accepted") that they changed their status.
func (u *User) SetStatus(status int) {
-
u.mux.Lock()
- friends := u.friends
u.status = status
u.mux.Unlock()
- //SEND STATUS CHANGE MESSAGE TO User's FRIENDS WHOM ARE "ACCEPTED"
- message := make(map[string]interface{})
- message[helpers.ServerActionFriendStatusChange] = make(map[string]interface{})
- message[helpers.ServerActionFriendStatusChange].(map[string]interface{})["n"] = u.name
- message[helpers.ServerActionFriendStatusChange].(map[string]interface{})["s"] = status
- for key, val := range friends {
- if val.RequestStatus() == database.FriendStatusAccepted {
- friend, friendErr := Get(key)
- if friendErr == nil {
- friend.mux.Lock()
- for _, conn := range friend.conns {
- (*conn).socket.WriteJSON(message)
- }
- friend.mux.Unlock()
- }
- }
+ // Send status to friends
+ message := map[string]map[string]interface{}{
+ helpers.ServerActionFriendStatusChange: {
+ "n": u.name,
+ "s": status,
+ },
}
+ u.sendToFriends(message)
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -590,7 +543,7 @@ func (u *User) Invite(invUser *User, connID string) error {
}
currRoom := (*u.conns[connID]).room
u.mux.Unlock()
- rType := rooms.GetRoomTypes()[currRoom.Type()]
+ rType := GetRoomTypes()[currRoom.Type()]
if currRoom == nil || currRoom.Name() == "" {
return errors.New("The user '" + u.name + "' is not in a room")
} else if !currRoom.IsPrivate() {
@@ -601,19 +554,21 @@ func (u *User) Invite(invUser *User, connID string) error {
return errors.New("Only the server can manipulate that type of room")
}
- //ADD TO INVITE LIST
+ // Add to invite list
addErr := currRoom.AddInvite(invUser.name)
if addErr != nil {
return addErr
}
- //MAKE INVITE MESSAGE
- invMessage := make(map[string]interface{})
- invMessage[helpers.ServerActionRoomInvite] = make(map[string]interface{})
- invMessage[helpers.ServerActionRoomInvite].(map[string]interface{})["u"] = u.name
- invMessage[helpers.ServerActionRoomInvite].(map[string]interface{})["r"] = currRoom.Name()
+ // Make response message
+ invMessage := map[string]map[string]interface{}{
+ helpers.ServerActionRoomInvite: {
+ "u": u.name,
+ "r": currRoom.Name(),
+ },
+ }
- //SEND MESSAGE
+ // Send response to all connections
invUser.mux.Lock()
for _, conn := range invUser.conns {
(*conn).socket.WriteJSON(invMessage)
@@ -646,7 +601,7 @@ func (u *User) RevokeInvite(revokeUser string, connID string) error {
}
currRoom := (*u.conns[connID]).room
u.mux.Unlock()
- rType := rooms.GetRoomTypes()[currRoom.Type()]
+ rType := GetRoomTypes()[currRoom.Type()]
if currRoom == nil || currRoom.Name() == "" {
return errors.New("The user '" + u.name + "' is not in a room")
} else if !currRoom.IsPrivate() {
@@ -657,7 +612,7 @@ func (u *User) RevokeInvite(revokeUser string, connID string) error {
return errors.New("Only the server can manipulate that type of room")
}
- //REMOVE FROM INVITE LIST
+ // Remove from invite list
removeErr := currRoom.RemoveInvite(revokeUser)
if removeErr != nil {
return removeErr
@@ -671,6 +626,14 @@ func (u *User) RevokeInvite(revokeUser string, connID string) error {
// User ATTRIBUTE READERS ////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
+// UserCount returns the number of Users logged into the server.
+func UserCount() int {
+ usersMux.Lock()
+ length := len(users)
+ usersMux.Unlock()
+ return length
+}
+
// Name gets the name of the User.
func (u *User) Name() string {
return u.name
@@ -696,7 +659,7 @@ func (u *User) Friends() map[string]database.Friend {
// RoomIn gets the Room that the User is currently in. A nil Room pointer means the User is not in a Room. If you are using MultiConnect in ServerSettings, the connID
// parameter is the connection ID associated with one of the connections attached to that User. This must
// be provided when getting a User's Room with MultiConnect enabled. Otherwise, an empty string can be used.
-func (u *User) RoomIn(connID string) *rooms.Room {
+func (u *User) RoomIn(connID string) *Room {
if multiConnect && len(connID) == 0 {
return nil
} else if !multiConnect {
@@ -738,76 +701,13 @@ func (u *User) IsGuest() bool {
return u.isGuest
}
-//////////////////////////////////////////////////////////////////////////////////////////////////////
-// SERVER STARTUP FUNCTIONS //////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////////////////////////////////
-
-// SetServerStarted is for Gopher Game Server internal mechanics only.
-func SetServerStarted(val bool) {
- if !serverStarted {
- serverStarted = val
- }
-}
-
-// SettingsSet is for Gopher Game Server internal mechanics only.
-func SettingsSet(kickDups bool, name string, deleteOnLeave bool, sqlFeat bool, remMe bool, multiConn bool) {
- if !serverStarted {
- kickOnLogin = kickDups
- serverName = name
- sqlFeatures = sqlFeat
- rememberMe = remMe
- multiConnect = multiConn
- rooms.SettingsSet(name, deleteOnLeave, multiConn)
- }
-}
-
-//////////////////////////////////////////////////////////////////////////////////////////////////////
-// SERVER PAUSE AND RESUME ///////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////////////////////////////////
-
-// Pause is only for internal Gopher Game Server mechanics.
-func Pause() {
- if !serverPaused {
- serverPaused = true
-
- //
- clientResp := helpers.MakeClientResponse(helpers.ClientActionLogout, nil, helpers.NewError("", 0))
- usersMux.Lock()
- for _, user := range users {
- (*user).mux.Lock()
- for connID, conn := range (*user).conns {
- //REMOVE CONNECTION FROM THEIR ROOM
- currRoom := (*conn).room
- if currRoom != nil && currRoom.Name() != "" {
- (*user).mux.Unlock()
- currRoom.RemoveUser((*user).name, connID)
- (*user).mux.Lock()
- }
-
- //LOG CONNECTION OUT
- (*conn).clientMux.Lock()
- if *((*conn).user) != nil {
- *((*conn).user) = nil
- }
- (*conn).clientMux.Unlock()
-
- //SEND LOG OUT MESSAGE
- (*conn).socket.WriteJSON(clientResp)
- }
- (*user).mux.Unlock()
- }
- users = make(map[string]*User)
- usersMux.Unlock()
-
- //
- serverStarted = false
- }
-}
-
-// Resume is only for internal Gopher Game Server mechanics.
-func Resume() {
- if serverPaused {
- serverStarted = true
- serverPaused = false
+// ConnectionIDs returns a []string of all the User's connection IDs
+func (u *User) ConnectionIDs() []string {
+ u.mux.Lock()
+ ids := make([]string, 0, len(u.conns))
+ for id := range u.conns {
+ ids = append(ids, id)
}
-}
+ u.mux.Unlock()
+ return ids
+}
\ No newline at end of file
diff --git a/users/vars.go b/core/vars.go
similarity index 61%
rename from users/vars.go
rename to core/vars.go
index 8ee6022..dd97e11 100644
--- a/users/vars.go
+++ b/core/vars.go
@@ -1,6 +1,7 @@
-package users
+package core
import (
+ "errors"
"github.com/hewiefreeman/GopherGameServer/helpers"
)
@@ -20,21 +21,26 @@ func (u *User) SetVariable(key string, value interface{}, connID string) {
} else if !multiConnect {
connID = "1"
}
- //MAKE CLIENT MESSAGE
- resp := make(map[string]interface{})
- resp["k"] = key
- resp["v"] = value
+ // Set the variable
u.mux.Lock()
if _, ok := u.conns[connID]; !ok {
u.mux.Unlock()
return
}
(*u.conns[connID]).vars[key] = value
- //SEND RESPONSE TO CLIENT
- clientResp := helpers.MakeClientResponse(helpers.ClientActionSetVariable, resp, helpers.NewError("", 0))
- (*u.conns[connID]).socket.WriteJSON(clientResp)
+ socket := (*u.conns[connID]).socket
u.mux.Unlock()
+
+ //MAKE CLIENT MESSAGE
+ resp := map[string]interface{}{
+ "k": key,
+ "v": value,
+ }
+ clientResp := helpers.MakeClientResponse(helpers.ClientActionSetVariable, resp, helpers.NoError())
+
+ //SEND RESPONSE TO CLIENT
+ socket.WriteJSON(clientResp)
}
// SetVariables sets all the specified User variables at once. The client API of the User will also receive these changes. If you are using MultiConnect in ServerSettings, the connID
@@ -49,6 +55,8 @@ func (u *User) SetVariables(values map[string]interface{}, connID string) {
} else if !multiConnect {
connID = "1"
}
+
+ // Set the variables
u.mux.Lock()
if _, ok := u.conns[connID]; !ok {
u.mux.Unlock()
@@ -57,10 +65,13 @@ func (u *User) SetVariables(values map[string]interface{}, connID string) {
for key, val := range values {
(*u.conns[connID]).vars[key] = val
}
- //SEND RESPONSE TO CLIENT
- clientResp := helpers.MakeClientResponse(helpers.ClientActionSetVariables, values, helpers.NewError("", 0))
- (*u.conns[connID]).socket.WriteJSON(clientResp)
+ socket := (*u.conns[connID]).socket
u.mux.Unlock()
+
+ //SEND RESPONSE TO CLIENT
+ clientResp := helpers.MakeClientResponse(helpers.ClientActionSetVariables, values, helpers.NoError())
+ socket.WriteJSON(clientResp)
+
}
// GetVariable gets one of the User's variables by it's key. If you are using MultiConnect in ServerSettings, the connID
@@ -71,6 +82,7 @@ func (u *User) GetVariable(key string, connID string) interface{} {
if len(key) == 0 {
return nil
}
+
u.mux.Lock()
val := (*u.conns[connID]).vars[key]
u.mux.Unlock()
@@ -99,3 +111,82 @@ func (u *User) GetVariables(keys []string, connID string) map[string]interface{}
//
return value
}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// ROOM VARIABLES /////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// SetVariable sets a Room variable.
+func (r *Room) SetVariable(key string, value interface{}) {
+ //REJECT INCORRECT INPUT
+ if len(key) == 0 {
+ return
+ }
+
+ r.mux.Lock()
+ if r.usersMap == nil {
+ r.mux.Unlock()
+ return
+ }
+ r.vars[key] = value
+ r.mux.Unlock()
+
+ //
+ return
+}
+
+// SetVariables sets all the specified Room variables at once.
+func (r *Room) SetVariables(values map[string]interface{}) {
+ r.mux.Lock()
+ if r.usersMap == nil {
+ r.mux.Unlock()
+ return
+ }
+ for key, val := range values {
+ r.vars[key] = val
+ }
+ r.mux.Unlock()
+
+ //
+ return
+}
+
+// GetVariable gets one of the Room's variables.
+func (r *Room) GetVariable(key string) (interface{}, error) {
+ //REJECT INCORRECT INPUT
+ if len(key) == 0 {
+ return nil, errors.New("*Room.GetVariable() requires a key")
+ }
+
+ r.mux.Lock()
+ if r.usersMap == nil {
+ r.mux.Unlock()
+ return nil, errors.New("Room '" + r.name + "' does not exist")
+ }
+ value := r.vars[key]
+ r.mux.Unlock()
+
+ //
+ return value, nil
+}
+
+// GetVariables gets all the specified (or all if not) Room variables as a map[string]interface{}.
+func (r *Room) GetVariables(keys []string) (map[string]interface{}, error) {
+ var value map[string]interface{} = make(map[string]interface{})
+ r.mux.Lock()
+ if r.usersMap == nil {
+ r.mux.Unlock()
+ return nil, errors.New("Room '" + r.name + "' does not exist")
+ }
+ if keys == nil || len(keys) == 0 {
+ value = r.vars
+ } else {
+ for i := 0; i < len(keys); i++ {
+ value[keys[i]] = r.vars[keys[i]]
+ }
+ }
+ r.mux.Unlock()
+
+ //
+ return value, nil
+}
diff --git a/database/authentication.go b/database/authentication.go
index 8c306d8..11db159 100644
--- a/database/authentication.go
+++ b/database/authentication.go
@@ -16,11 +16,16 @@ var (
customAccountInfoChangeRequirements map[string]struct{} = make(map[string]struct{})
customDeleteAccountRequirements map[string]struct{} = make(map[string]struct{})
- SignUpCallback func(string, map[string]interface{}) bool // SignUpCallback is only for internal Gopher Game Server mechanics.
- LoginCallback func(string, int, map[string]interface{}, map[string]interface{}) bool // LoginCallback is only for internal Gopher Game Server mechanics.
- DeleteAccountCallback func(string, int, map[string]interface{}, map[string]interface{}) bool // DeleteAccountCallback is only for internal Gopher Game Server mechanics.
- AccountInfoChangeCallback func(string, int, map[string]interface{}, map[string]interface{}) bool // AccountInfoChangeCallback is only for internal Gopher Game Server mechanics.
- PasswordChangeCallback func(string, int, map[string]interface{}, map[string]interface{}) bool // PasswordChangeCallback is only for internal Gopher Game Server mechanics.
+ // SignUpCallback is only for internal Gopher Game Server mechanics.
+ SignUpCallback func(string, map[string]interface{}) bool
+ // LoginCallback is only for internal Gopher Game Server mechanics.
+ LoginCallback func(string, int, map[string]interface{}, map[string]interface{}) bool
+ // DeleteAccountCallback is only for internal Gopher Game Server mechanics.
+ DeleteAccountCallback func(string, int, map[string]interface{}, map[string]interface{}) bool
+ // AccountInfoChangeCallback is only for internal Gopher Game Server mechanics.
+ AccountInfoChangeCallback func(string, int, map[string]interface{}, map[string]interface{}) bool
+ // PasswordChangeCallback is only for internal Gopher Game Server mechanics.
+ PasswordChangeCallback func(string, int, map[string]interface{}, map[string]interface{}) bool
)
// Authentication error messages
@@ -34,6 +39,7 @@ const (
errorInsufficientCols = "Insufficient custom column data"
errorIncorrectLogin = "Incorrect login or password"
errorInvalidAutoLog = "Invalid auto-login data"
+ errorNoShardFound = "Could not find a master or healthy replica database"
)
//////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -146,6 +152,10 @@ func checkCustomRequirements(customCols map[string]interface{}, requirements map
return true
}
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+// QUERY HELPERS /////////////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+
//////////////////////////////////////////////////////////////////////////////////////////////////////
// SIGN A USER UP ////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -176,11 +186,13 @@ func SignUpClient(userName string, password string, customCols map[string]interf
return helpers.NewError(hashErr.Error(), helpers.ErrorAuthEncryption)
}
- var vals []interface{} = []interface{}{}
+ var vals []interface{}
//CREATE PART 1 OF QUERY
queryPart1 := "INSERT INTO " + tableUsers + " (" + usersColumnName + ", " + usersColumnPassword + ", "
+
if customCols != nil {
+ vals = make([]interface{}, 0, len(customCols))
if customLoginColumn != "" {
if _, ok := customCols[customLoginColumn]; !ok {
return helpers.NewError(errorInsufficientCols, helpers.ErrorAuthInsufficientCols)
@@ -227,7 +239,7 @@ func SignUpClient(userName string, password string, customCols map[string]interf
}
//
- return helpers.NewError("", 0)
+ return helpers.NoError()
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -252,23 +264,33 @@ func LoginClient(userName string, password string, deviceTag string, remMe bool,
}
//FIRST THREE ARE id, password, name IN THAT ORDER
- var vals []interface{} = []interface{}{new(int), new([]byte), new(string)}
+ var vals []interface{}
//CONSTRUCT SELECT QUERY
selectQuery := "Select " + usersColumnID + ", " + usersColumnPassword + ", " + usersColumnName + ", "
+
if customCols != nil {
+ vals = make([]interface{}, 0, len(customCols)+3)
+ vals = append(vals, new(int), new([]byte), new(string))
for key := range customCols {
selectQuery = selectQuery + key + ", "
//MAINTAIN THE ORDER IN WHICH THE COLUMNS WERE DECLARED VIA A SLICE
vals = append(vals, new(interface{}))
}
+ } else {
+ vals = make([]interface{}, 0, 3)
+ vals = append(vals, new(int), new([]byte), new(string))
}
+
+ // GET LOGIN COLUMN AND TABLE NAME
+ var loginCol string = usersColumnName
+ var tableName string = tableUsers
if len(customLoginColumn) > 0 {
- selectQuery = selectQuery[0:len(selectQuery)-2] + " FROM " + tableUsers + " WHERE " + customLoginColumn + "=\"" + userName + "\" LIMIT 1;"
- } else {
- selectQuery = selectQuery[0:len(selectQuery)-2] + " FROM " + tableUsers + " WHERE " + usersColumnName + "=\"" + userName + "\" LIMIT 1;"
+ loginCol = customLoginColumn
}
+ selectQuery = selectQuery[0:len(selectQuery)-2] + " FROM " + tableName + " WHERE " + loginCol + "=\"" + userName + "\" LIMIT 1;"
+
//EXECUTE SELECT QUERY
checkRows, err := database.Query(selectQuery)
if err != nil {
@@ -318,13 +340,18 @@ func LoginClient(userName string, password string, deviceTag string, remMe bool,
//MAKE AUTO-LOG ENTRY
devicePass, devicePassErr = helpers.GenerateSecureString(32)
if devicePassErr == nil {
- database.Exec("INSERT INTO " + tableAutologs + " (" + autologsColumnID + ", " + autologsColumnDeviceTag + ", " + autologsColumnDevicePass +
+ _, exErr := database.Exec("INSERT INTO " + tableAutologs + " (" + autologsColumnID + ", " + autologsColumnDeviceTag + ", " + autologsColumnDevicePass +
") VALUES (" + strconv.Itoa(*dbIndex) + ", \"" + deviceTag + "\", \"" + devicePass + "\");")
+ if exErr != nil {
+ ////// LOG ERROR!!!!!!
+ }
+ } else {
+ ////// LOG ERROR!!!!!!
}
}
//
- return *uName, *dbIndex, devicePass, helpers.NewError("", 0)
+ return *uName, *dbIndex, devicePass, helpers.NoError()
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -339,11 +366,14 @@ func AutoLoginClient(tag string, pass string, newPass string, dbID int) (string,
if checkStringSQLInjection(tag) {
return "", helpers.NewError(errorMaliciousChars, helpers.ErrorAuthMaliciousChars)
}
+
//EXECUTE SELECT QUERY
var dPass string
- checkRows, err := database.Query("Select " + autologsColumnDevicePass + " FROM " + tableAutologs + " WHERE " + autologsColumnID + "=" + strconv.Itoa(dbID) + " AND " +
+ db := database
+ tableName := tableAutologs
+ checkRows, checkErr := db.Query("Select " + autologsColumnDevicePass + " FROM " + tableName + " WHERE " + autologsColumnID + "=" + strconv.Itoa(dbID) + " AND " +
autologsColumnDeviceTag + "=\"" + tag + "\" LIMIT 1;")
- if err != nil {
+ if checkErr != nil {
return "", helpers.NewError(errorInvalidAutoLog, helpers.ErrorDatabaseInvalidAutolog)
}
//
@@ -358,19 +388,21 @@ func AutoLoginClient(tag string, pass string, newPass string, dbID int) (string,
if pass != dPass {
//SOMEONE TRIED TO COMPROMISE THIS KEY PAIR. DELETE IT NOW.
RemoveAutoLog(dbID, tag)
+ return "", helpers.NewError(errorInvalidAutoLog, helpers.ErrorDatabaseInvalidAutolog)
}
//UPDATE TO NEW PASS
- _, updateErr := database.Exec("UPDATE " + tableAutologs + " SET " + autologsColumnDevicePass + "=\"" + newPass + "\" WHERE " + autologsColumnID + "=" + strconv.Itoa(dbID) + " AND " +
+ _, updateErr := db.Exec("UPDATE " + tableName + " SET " + autologsColumnDevicePass + "=\"" + newPass + "\" WHERE " + autologsColumnID + "=" + strconv.Itoa(dbID) + " AND " +
autologsColumnDeviceTag + "=\"" + tag + "\" LIMIT 1;")
if updateErr != nil {
return "", helpers.NewError(errorInvalidAutoLog, helpers.ErrorDatabaseInvalidAutolog)
}
//EVERYTHING WENT WELL, GET THE User's NAME
+ tableName = tableUsers
var userName string
- userRows, err := database.Query("Select " + usersColumnName + " FROM " + tableUsers + " WHERE " + usersColumnID + "=" + strconv.Itoa(dbID) + " LIMIT 1;")
- if err != nil {
+ userRows, usrErr := db.Query("Select " + usersColumnName + " FROM " + tableName + " WHERE " + usersColumnID + "=" + strconv.Itoa(dbID) + " LIMIT 1;")
+ if usrErr != nil {
return "", helpers.NewError(errorInvalidAutoLog, helpers.ErrorDatabaseInvalidAutolog)
}
//
@@ -387,7 +419,7 @@ func AutoLoginClient(tag string, pass string, newPass string, dbID int) (string,
}
//
- return userName, helpers.NewError("", 0)
+ return userName, helpers.NoError()
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -427,18 +459,24 @@ func ChangePassword(userName string, password string, newPassword string, custom
}
//FIRST TWO ARE id, password IN THAT ORDER
- var vals []interface{} = []interface{}{new(int), new([]byte)}
- var valsList []interface{} = []interface{}{}
+ var vals []interface{}
+ var valsList []interface{}
//CONSTRUCT SELECT QUERY
selectQuery := "Select " + usersColumnID + ", " + usersColumnPassword + ", "
if customCols != nil {
+ vals = make([]interface{}, 0, len(customCols)+2)
+ vals = append(vals, new(int), new([]byte))
+ valsList = make([]interface{}, 0, len(customCols))
for key, val := range customCols {
selectQuery = selectQuery + key + ", "
//MAINTAIN THE ORDER IN WHICH THE COLUMNS WERE DECLARED VIA A SLICE
vals = append(vals, new(interface{}))
valsList = append(valsList, []interface{}{val, customAccountInfo[key].dataType, key})
}
+ } else {
+ vals = make([]interface{}, 0, 2)
+ vals = append(vals, new(int), new([]byte))
}
selectQuery = selectQuery[0:len(selectQuery)-2] + " FROM " + tableUsers + " WHERE " + usersColumnName + "=\"" + userName + "\" LIMIT 1;"
@@ -495,7 +533,7 @@ func ChangePassword(userName string, password string, newPassword string, custom
}
//
- return helpers.NewError("", 0)
+ return helpers.NoError()
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -520,18 +558,24 @@ func ChangeAccountInfo(userName string, password string, customCols map[string]i
}
//FIRST TWO ARE id, password IN THAT ORDER
- var vals []interface{} = []interface{}{new(int), new([]byte)}
- var valsList []interface{} = []interface{}{}
+ var vals []interface{}
+ var valsList []interface{}
//CONSTRUCT SELECT QUERY
selectQuery := "Select " + usersColumnID + ", " + usersColumnPassword + ", "
if customCols != nil {
+ vals = make([]interface{}, 0, len(customCols)+2)
+ vals = append(vals, new(int), new([]byte))
+ valsList = make([]interface{}, 0, len(customCols))
for key, val := range customCols {
selectQuery = selectQuery + key + ", "
//MAINTAIN THE ORDER IN WHICH THE COLUMNS WERE DECLARED VIA A SLICE
vals = append(vals, new(interface{}))
valsList = append(valsList, []interface{}{val, customAccountInfo[key].dataType, key})
}
+ } else {
+ vals = make([]interface{}, 0, 2)
+ vals = append(vals, new(int), new([]byte))
}
selectQuery = selectQuery[0:len(selectQuery)-2] + " FROM " + tableUsers + " WHERE " + usersColumnName + "=\"" + userName + "\" LIMIT 1;"
@@ -596,7 +640,7 @@ func ChangeAccountInfo(userName string, password string, customCols map[string]i
}
//
- return helpers.NewError("", 0)
+ return helpers.NoError()
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -619,16 +663,21 @@ func DeleteAccount(userName string, password string, customCols map[string]inter
}
//FIRST TWO ARE id, password IN THAT ORDER
- var vals []interface{} = []interface{}{new(int), new([]byte)}
+ var vals []interface{}
//CONSTRUCT SELECT QUERY
selectQuery := "Select " + usersColumnID + ", " + usersColumnPassword + ", "
if customCols != nil {
+ vals = make([]interface{}, 0, len(customCols)+2)
+ vals = append(vals, new(int), new([]byte))
for key := range customCols {
selectQuery = selectQuery + key + ", "
//MAINTAIN THE ORDER IN WHICH THE COLUMNS WERE DECLARED VIA A SLICE
vals = append(vals, new(interface{}))
}
+ } else {
+ vals = make([]interface{}, 0, 2)
+ vals = append(vals, new(int), new([]byte))
}
selectQuery = selectQuery[0:len(selectQuery)-2] + " FROM " + tableUsers + " WHERE " + usersColumnName + "=\"" + userName + "\" LIMIT 1;"
@@ -682,5 +731,5 @@ func DeleteAccount(userName string, password string, customCols map[string]inter
}
//
- return helpers.NewError("", 0)
+ return helpers.NoError()
}
diff --git a/database/database.go b/database/database.go
index 2b4fc18..396513f 100644
--- a/database/database.go
+++ b/database/database.go
@@ -17,11 +17,11 @@ var (
database *sql.DB
//SERVER SETTINGS
- serverStarted bool = false
- serverPaused bool = false
- rememberMe bool = false
- databaseName string
- inited bool = false
+ serverStarted bool = false
+ serverPaused bool = false
+ rememberMe bool = false
+ databaseName string = "gopherDB"
+ inited bool = false
)
//TABLE & COLUMN NAMES
@@ -77,24 +77,25 @@ func Init(userName string, password string, dbName string, protocol string, ip s
var err error
//OPEN THE DATABASE
- var openErr error
- database, openErr = sql.Open("mysql", userName+":"+password+"@"+protocol+"("+ip+":"+strconv.Itoa(port)+")/"+dbName)
+ database, err = sql.Open("mysql", userName+":"+password+"@"+protocol+"("+ip+":"+strconv.Itoa(port)+")/"+dbName)
if err != nil {
- return openErr
+ return err
}
//NOTE: Open doesn't open a connection.
//MUST PING TO CHECK IF FOUND DATABASE
- pingErr := database.Ping()
- if pingErr != nil {
+ err = database.Ping()
+ if err != nil {
return errors.New("Could not connect to database!")
}
- databaseName = dbName
+ if len(dbName) != 0 {
+ databaseName = dbName
+ }
//CONFIGURE DATABASE
- setupErr := setUp()
- if setupErr != nil {
- return setupErr
+ err = setUp()
+ if err != nil {
+ return err
}
//
diff --git a/database/friending.go b/database/friending.go
index 7e25447..d431f91 100644
--- a/database/friending.go
+++ b/database/friending.go
@@ -99,30 +99,34 @@ func GetFriends(userIndex int) (map[string]*Friend, error) {
//EXECUTE SELECT QUERY
friendRows, friendRowsErr := database.Query("Select " + friendsColumnFriend + ", " + friendsColumnStatus + " FROM " + tableFriends + " WHERE " + friendsColumnUser + "=" + strconv.Itoa(userIndex) + ";")
if friendRowsErr != nil {
- return friends, friendRowsErr
+ return nil, friendRowsErr
}
- defer friendRows.Close()
//
for friendRows.Next() {
var friendName string
var friendID int
var friendStatus int
if scanErr := friendRows.Scan(&friendID, &friendStatus); scanErr != nil {
- return friends, scanErr
+ friendRows.Close()
+ return nil, scanErr
}
//
friendInfoRows, friendInfoErr := database.Query("Select " + usersColumnName + " FROM " + tableUsers + " WHERE " + usersColumnID + "=" + strconv.Itoa(friendID) + " LIMIT 1;")
if friendInfoErr != nil {
- return friends, friendInfoErr
+ friendRows.Close()
+ return nil, friendInfoErr
}
- defer friendInfoRows.Close()
friendInfoRows.Next()
if scanErr := friendInfoRows.Scan(&friendName); scanErr != nil {
- return friends, scanErr
+ friendRows.Close()
+ friendInfoRows.Close()
+ return nil, scanErr
}
+ friendInfoRows.Close()
aFriend := Friend{name: friendName, dbID: friendID, status: friendStatus}
friends[friendName] = &aFriend
}
+ friendRows.Close()
//
return friends, nil
}
diff --git a/database/setup.go b/database/setup.go
index 242e544..5c37215 100644
--- a/database/setup.go
+++ b/database/setup.go
@@ -5,148 +5,160 @@ import (
"strconv"
)
-// CONFIGURES THE DATABASE FOR Gopher Game Server USAGE
+// Configures the SQL database for Gopher Game Server
func setUp() error {
- //CHECK IF THE TABLE users HAS BEEN MADE
+ // Check if the users table has been created
_, checkErr := database.Exec("SELECT " + usersColumnName + " FROM " + tableUsers + " WHERE " + usersColumnID + "=1;")
if checkErr != nil {
- fmt.Println("Making all tables...")
- //MAKE THE users TABLE QUERY
- createQuery := "CREATE TABLE " + tableUsers + " (" +
- usersColumnID + " INTEGER NOT NULL AUTO_INCREMENT, " +
- usersColumnName + " VARCHAR(255) UNIQUE NOT NULL, " +
- usersColumnPassword + " VARCHAR(255) NOT NULL, "
-
- //APPEND custom AccountInfoColumn ITEMS
- for key, val := range customAccountInfo {
- createQuery = createQuery + key + " " + dataTypes[val.dataType]
- //CHECK IF NEEDS maxSize/precision
- if isSizeDataType(val.dataType) {
- createQuery = createQuery + "(" + strconv.Itoa(val.maxSize) + ")"
- } else if isPrecisionDataType(val.dataType) {
- createQuery = createQuery + "(" + strconv.Itoa(val.maxSize) + ", " + strconv.Itoa(val.precision) + ")"
- }
- //CHECK IF UNIQUE
- if val.unique {
- createQuery = createQuery + " UNIQUE"
- }
- //CHECK IF NOT NULL
- if val.notNull {
- createQuery = createQuery + " NOT NULL, "
- } else {
- createQuery = createQuery + ", "
+ fmt.Println("Creating \"" + tableUsers + "\" table...")
+ // Make the users table
+ if cErr := createUserTableSQL(); cErr != nil {
+ return cErr
+ }
+ }
+ // Check for new custom AccountInfoColumn items
+ if newItemsErr := addNewCustomItemsSQL(); newItemsErr != nil {
+ return newItemsErr
+ }
+
+ if rememberMe {
+ // Check if autologs table has been made
+ _, checkErr := database.Exec("SELECT " + autologsColumnID + " FROM " + tableAutologs + " WHERE " + autologsColumnID + "=1;")
+ if checkErr != nil {
+ fmt.Println("Making autologs table...")
+ if cErr := createAutologsTableSQL(); cErr != nil {
+ return cErr
}
}
+ }
+ // Make sure customLoginColumn is unique if it is set
+ if len(customLoginColumn) > 0 {
+ _, alterErr := database.Exec("ALTER TABLE " + tableUsers + " ADD UNIQUE (" + customLoginColumn + ");")
+ if alterErr != nil {
+ return alterErr
+ }
+ }
- createQuery = createQuery + "PRIMARY KEY (" + usersColumnID + "));"
+ //
+ return nil
+}
- //EXECUTE users TABLE QUERY
- _, createErr := database.Exec(createQuery)
- if createErr != nil {
- return createErr
- }
+func createUserTableSQL() error {
+ createQuery := "CREATE TABLE " + tableUsers + " (" +
+ usersColumnID + " INTEGER NOT NULL AUTO_INCREMENT, " +
+ usersColumnName + " VARCHAR(255) UNIQUE NOT NULL, " +
+ usersColumnPassword + " VARCHAR(255) NOT NULL, "
- //ADJUST AUTO_INCREMENT TO 1
- _, adjustErr := database.Exec("ALTER TABLE " + tableUsers + " AUTO_INCREMENT=1;")
- if adjustErr != nil {
- return adjustErr
+ // Append custom AccountInfoColumn items
+ for key, val := range customAccountInfo {
+ createQuery = createQuery + key + " " + dataTypes[val.dataType]
+ // Check for maxSize/precision
+ if isSizeDataType(val.dataType) {
+ createQuery = createQuery + "(" + strconv.Itoa(val.maxSize) + ")"
+ } else if isPrecisionDataType(val.dataType) {
+ createQuery = createQuery + "(" + strconv.Itoa(val.maxSize) + ", " + strconv.Itoa(val.precision) + ")"
}
-
- //MAKE THE friends TABLE
- _, friendsErr := database.Exec("CREATE TABLE " + tableFriends + " (" +
- friendsColumnUser + " INTEGER NOT NULL, " +
- friendsColumnFriend + " INTEGER NOT NULL, " +
- friendsColumnStatus + " INTEGER NOT NULL, " +
- "FOREIGN KEY(" + friendsColumnUser + ") REFERENCES " + tableUsers + "(" + usersColumnID + "), " +
- "FOREIGN KEY(" + friendsColumnFriend + ") REFERENCES " + tableUsers + "(" + usersColumnID + ")" +
- ");")
- if friendsErr != nil {
- return friendsErr
+ // Check for unique
+ if val.unique {
+ createQuery = createQuery + " UNIQUE"
}
+ // Check for not-null
+ if val.notNull {
+ createQuery = createQuery + " NOT NULL, "
+ } else {
+ createQuery = createQuery + ", "
+ }
+ }
- if rememberMe {
- //MAKE THE autologs TABLE
- _, friendsErr = database.Exec("CREATE TABLE " + tableAutologs + " (" +
- autologsColumnID + " INTEGER NOT NULL, " +
- autologsColumnDevicePass + " VARCHAR(255) NOT NULL, " +
- autologsColumnDeviceTag + " VARCHAR(255) NOT NULL, " +
- ");")
- if friendsErr != nil {
- return friendsErr
- }
+ createQuery = createQuery + "PRIMARY KEY (" + usersColumnID + "));"
+
+ // Execute users table query
+ _, createErr := database.Exec(createQuery)
+ if createErr != nil {
+ return createErr
+ }
+
+ // Adjust auto-increment to 1
+ _, adjustErr := database.Exec("ALTER TABLE " + tableUsers + " AUTO_INCREMENT=1;")
+ if adjustErr != nil {
+ return adjustErr
+ }
+
+ // Make friends table
+ if _, friendsErr := database.Exec("CREATE TABLE " + tableFriends + " (" +
+ friendsColumnUser + " INTEGER NOT NULL, " +
+ friendsColumnFriend + " INTEGER NOT NULL, " +
+ friendsColumnStatus + " INTEGER NOT NULL" +
+ ");"); friendsErr != nil {
+
+ return friendsErr
+ }
+
+ if rememberMe {
+ if cErr := createAutologsTableSQL(); cErr != nil {
+ return cErr
}
+ }
+
+ return nil
+}
- } else {
- //CHECK IF THERE ARE ANY NEW custom AccountInfoColumn ITEMS
- query := "ALTER TABLE " + tableUsers + " "
- var execQuery bool = false
+func createAutologsTableSQL() error {
+ if _, aErr := database.Exec("CREATE TABLE " + tableAutologs + " (" +
+ autologsColumnID + " INTEGER NOT NULL, " +
+ autologsColumnDevicePass + " VARCHAR(255) NOT NULL, " +
+ autologsColumnDeviceTag + " VARCHAR(255) NOT NULL, " +
+ ");"); aErr != nil {
+
+ return aErr
+ }
+ return nil
+}
+
+func addNewCustomItemsSQL() error {
+ query := "ALTER TABLE " + tableUsers + " "
+ var execQuery bool
+ //
+ for key, val := range customAccountInfo {
+ // Check if item exists
+ checkRows, err := database.Query("SHOW COLUMNS FROM " + tableUsers + " LIKE '" + key + "';")
+ if err != nil {
+ return err
+ }
//
- for key, val := range customAccountInfo {
- //CHECK IF COLUMN EXISTS
- checkRows, err := database.Query("SHOW COLUMNS FROM " + tableUsers + " LIKE '" + key + "';")
- if err != nil {
- return err
- }
- //
- checkRows.Next()
- _, colsErr := checkRows.Columns()
- if colsErr != nil {
- fmt.Println("Adding AccountInfoColumn '" + key + "'...")
- //THIS customAccountInfo COLUMN DOES NOT EXIST... YET, MY NERD.
- query = query + "ADD COLUMN " + key + " " + dataTypes[val.dataType]
- if isSizeDataType(val.dataType) {
- query = query + "(" + strconv.Itoa(val.maxSize) + ")"
- } else if isPrecisionDataType(val.dataType) {
- query = query + "(" + strconv.Itoa(val.maxSize) + ", " + strconv.Itoa(val.precision) + ")"
- }
- //CHECK IF UNIQUE
- if val.unique {
- query = query + " UNIQUE"
- }
- //CHECK IF NOT NULL
- if val.notNull {
- query = query + " NOT NULL, "
- } else {
- query = query + ", "
- }
- execQuery = true
+ checkRows.Next()
+ _, colsErr := checkRows.Columns()
+ if colsErr != nil {
+ // The item doesn't exist yet...
+ fmt.Println("Adding AccountInfoColumn '" + key + "'...")
+ query = query + "ADD COLUMN " + key + " " + dataTypes[val.dataType]
+ if isSizeDataType(val.dataType) {
+ query = query + "(" + strconv.Itoa(val.maxSize) + ")"
+ } else if isPrecisionDataType(val.dataType) {
+ query = query + "(" + strconv.Itoa(val.maxSize) + ", " + strconv.Itoa(val.precision) + ")"
}
- checkRows.Close()
- }
- if execQuery {
- //MAKE THE NEW COLUMNS
- query = query[0:len(query)-2] + ";"
- _, colsErr := database.Exec(query)
- if colsErr != nil {
- return colsErr
+ // Unique check
+ if val.unique {
+ query = query + " UNIQUE"
}
- }
-
- if rememberMe {
- //CHECK IF THE autologs TABLE HAS BEEN MADE
- _, checkErr := database.Exec("SELECT " + autologsColumnID + " FROM " + tableAutologs + " WHERE " + autologsColumnID + "=1;")
- if checkErr != nil {
- fmt.Println("Making autologs table...")
- //MAKE THE autologs TABLE
- _, friendsErr := database.Exec("CREATE TABLE " + tableAutologs + " (" +
- autologsColumnID + " INTEGER NOT NULL, " +
- autologsColumnDevicePass + " VARCHAR(255) NOT NULL, " +
- autologsColumnDeviceTag + " VARCHAR(255) NOT NULL" +
- ");")
- if friendsErr != nil {
- return friendsErr
- }
+ // Not-null check
+ if val.notNull {
+ query = query + " NOT NULL, "
+ } else {
+ query = query + ", "
}
+ execQuery = true
}
+ checkRows.Close()
}
- //MAKE SURE THE customLoginColumn IS UNIQUE IF SET
- if len(customLoginColumn) > 0 {
- _, alterErr := database.Exec("ALTER TABLE " + tableUsers + " ADD UNIQUE (" + customLoginColumn + ");")
- if alterErr != nil {
- return alterErr
+ if execQuery {
+ // Make new columns
+ query = query[0:len(query)-2] + ";"
+ _, colsErr := database.Exec(query)
+ if colsErr != nil {
+ return colsErr
}
}
- //
return nil
-}
+}
\ No newline at end of file
diff --git a/docs/_config.yml b/docs/_config.yml
new file mode 100644
index 0000000..c741881
--- /dev/null
+++ b/docs/_config.yml
@@ -0,0 +1 @@
+theme: jekyll-theme-slate
\ No newline at end of file
diff --git a/docs/css/nav.css b/docs/css/nav.css
new file mode 100644
index 0000000..8fbdd1b
--- /dev/null
+++ b/docs/css/nav.css
@@ -0,0 +1,41 @@
+.nav {
+ display: block;
+ position: absolute;
+
+ top: 0px;
+ right: 0px;
+
+ height: 40px;
+ min-width: 320px;
+
+ text-align: right;
+
+ margin-right: 10px;
+
+ overflow: hidden;
+}
+
+.navButton {
+ display: inline-block;
+ position: relative;
+
+ height: 30px;
+
+ line-height: 30px;
+ text-align: center;
+ vertical-align: center;
+ align-self: right;
+
+ padding-left: 5px;
+ padding-right: 5px;
+ margin-right: 3px;
+
+ color: #ffffff;
+ background: #3f3f3f;
+ box-shadow: 0px 3px 3px rgba(0, 0, 0, 0.4);
+
+ border-bottom: 3px solid #85d681;
+ border-radius: 0px 0px 2px 2px;
+
+ cursor: pointer;
+}
\ No newline at end of file
diff --git a/docs/css/shell.css b/docs/css/shell.css
new file mode 100644
index 0000000..c59189c
--- /dev/null
+++ b/docs/css/shell.css
@@ -0,0 +1,279 @@
+body {
+ font-family: 'Open Sans', sans-serif;
+ font-weight: 600;
+ font-size: 16px;
+ color: #ffffff;
+
+ background: #e3e3e3;
+
+ margin: 0px;
+ padding: 0px;
+
+ overflow: scroll;
+}
+
+br {
+ margin: 0px;
+ padding: 0px;
+}
+
+p {
+ margin: 0px;
+ padding: 0px;
+}
+
+a {
+ color: #ffffff;
+ text-decoration: none;
+}
+
+a:link {
+ color: #ffffff;
+ text-decoration: none;
+}
+
+a:hover {
+ color: #dedede;
+ text-decoration: underline;
+}
+
+a:active {
+ color: #ffffff;
+ text-decoration: none;
+}
+
+h1 {
+ font-family: 'Open Sans', sans-serif;
+ font-weight: 700;
+ font-size: 36px;
+ line-height: 36px;
+
+ margin: 0px;
+ padding: 0px;
+
+ color: #85d681;
+}
+
+h2 {
+ font-family: 'Open Sans', sans-serif;
+ font-weight: 600;
+ font-size: 30px;
+
+ margin: 0px;
+ padding: 0px;
+}
+
+h3 {
+ font-family: 'Open Sans', sans-serif;
+ font-weight: 650;
+ font-size: 24px;
+ line-height: 24px;
+
+ margin-top: 5px;
+ margin-bottom: 0px;
+ padding: 0px;
+
+ color: #3f3f3f;
+}
+
+h4 {
+ font-family: 'Open Sans', sans-serif;
+ font-weight: 650;
+ font-size: 16px;
+ line-height: 16px;
+
+ margin: 0px;
+ padding: 0px;
+
+ color: #3f3f3f;
+}
+
+h5 {
+ font-family: 'Open Sans', sans-serif;
+ font-weight: 600;
+ font-size: 12px;
+ line-height: 12px;
+
+ margin-top: 5px;
+ margin-bottom: 0px;
+ padding: 0px;
+
+ color: #ffffff;
+}
+
+.wrapper {
+ display: block;
+ position: absolute;
+
+ text-align: center;
+
+ top: 0px;
+ left: 0px;
+
+ min-width: 310px;
+ width: 100%;
+
+ background: rgba(255,255,255,0);
+
+ z-index: 2;
+}
+
+.shadowWrapper {
+ display: block;
+ position: fixed;
+
+ text-align: center;
+
+ top: 0px;
+ left: 0px;
+
+ width: 100%;
+ height: 100%;
+
+ overflow: hidden;
+
+ z-index: 1;
+}
+
+.shadowObj {
+ display: block;
+
+ width: 100%;
+ max-width: 1000px;
+ height: 100%;
+
+ background: #ECFFEB;
+
+ box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.4);
+}
+
+.innerWrapper {
+ display: block;
+
+ width: 100%;
+ max-width: 1000px;
+
+ flex-direction: column;
+ flex-wrap: nowrap;
+ justify-content: flex-start;
+ align-items: stretch;
+}
+
+.topBar {
+ display: block;
+ position: relative;
+
+ height: 200px;
+ width: 100%;
+
+ /*background-image: url("../images/bg.jpg");*/
+ background-color: #7a7a7a;
+
+ border-bottom: 3px solid #85d681;
+
+ z-index: 6;
+}
+
+.content {
+ display: block;
+ position: relative;
+
+ width: 100%;
+ min-height: 100px;
+ height: 10px;
+
+ background: #ECFFEB;
+
+ overflow: hidden;
+}
+
+.logoHolder {
+ display: block;
+ position: absolute;
+
+ bottom: -30px;
+ left: 0px;
+
+ width: 230px;
+ height: 230px;
+
+ background-image: url('../images/icon_large.png');
+ background-repeat: no-repeat;
+ background-position: 0px -30px;
+ background-size: 100%;
+}
+
+.titleHolder {
+ display: block;
+ position: absolute;
+
+ bottom: 0px;
+ left: 220px;
+
+ margin-top: 10px;
+
+ width: 180px;
+ height: 160px;
+
+ vertical-align: center;
+ text-align: center;
+}
+
+.titleGhub {
+ display: block;
+ position: relative;
+
+ height: 50px;
+
+ margin-top: 10px;
+}
+
+.headerBtnsHolder {
+ display: block;
+ position: absolute;
+
+ vertical-align: center;
+ line-height: 155px;
+
+ bottom: 0px;
+ right: 0px;
+ left: 410px;
+
+ height: 140px;
+
+ white-space: nowrap;
+}
+
+.headerBtn {
+ display: inline-block;
+ position: relative;
+
+ width: 100px;
+ height: 140px;
+
+ margin-right: 20px;
+
+ align-self: center;
+}
+
+.headerBtnImage {
+ display: block;
+ position: relative;
+
+ width: 100px;
+ height: 100px;
+
+ background-repeat: no-repeat;
+ background-size: 100%;
+}
+
+.filler {
+ display: block;
+ position: absolute;
+
+ top: 0px;
+ left: 0px;
+
+ width: 100%;
+ height: 100%;
+}
\ No newline at end of file
diff --git a/docs/images/GitHub_Dark.png b/docs/images/GitHub_Dark.png
new file mode 100644
index 0000000..ea6ff54
Binary files /dev/null and b/docs/images/GitHub_Dark.png differ
diff --git a/docs/images/GitHub_Light.png b/docs/images/GitHub_Light.png
new file mode 100644
index 0000000..192846a
Binary files /dev/null and b/docs/images/GitHub_Light.png differ
diff --git a/docs/images/Gopher Title.png b/docs/images/Gopher Title.png
new file mode 100644
index 0000000..f59ceb9
Binary files /dev/null and b/docs/images/Gopher Title.png differ
diff --git a/docs/images/icon.png b/docs/images/icon.png
new file mode 100644
index 0000000..46b83c7
Binary files /dev/null and b/docs/images/icon.png differ
diff --git a/Server Gopher.png b/docs/images/icon_large.png
similarity index 100%
rename from Server Gopher.png
rename to docs/images/icon_large.png
diff --git a/docs/images/minimal_code_ex.png b/docs/images/minimal_code_ex.png
new file mode 100644
index 0000000..a0268c6
Binary files /dev/null and b/docs/images/minimal_code_ex.png differ
diff --git a/docs/images/running_ex.png b/docs/images/running_ex.png
new file mode 100644
index 0000000..ab152fb
Binary files /dev/null and b/docs/images/running_ex.png differ
diff --git a/docs/images/wiki_img.png b/docs/images/wiki_img.png
new file mode 100644
index 0000000..2819403
Binary files /dev/null and b/docs/images/wiki_img.png differ
diff --git a/docs/images/zip_file.png b/docs/images/zip_file.png
new file mode 100644
index 0000000..5143a05
Binary files /dev/null and b/docs/images/zip_file.png differ
diff --git a/docs/index.html b/docs/index.html
new file mode 100644
index 0000000..cded8e9
--- /dev/null
+++ b/docs/index.html
@@ -0,0 +1,61 @@
+
+
+ Gopher Game Server
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+