Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/net/local_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ var localGame = struct {

// StartLocalGame starts a private one-player server in this process. The
// multiplayer socket is suspended until StopLocalGame is called.
func StartLocalGame(seed int64) error {
func StartLocalGame() error {
if game.PlayerData == nil {
return fmt.Errorf("start local game: player data is not loaded")
}
Expand All @@ -46,7 +46,6 @@ func StartLocalGame(seed int64) error {
if err := transport.send(&packets.C2SCreateGamePacket{
Public: false,
MaxPlayers: 1,
Seed: seed,
}); err != nil {
stopLocalGame(true)
return fmt.Errorf("create local game: %w", err)
Expand Down
1 change: 0 additions & 1 deletion src/net/packets/game_lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import "github.com/threeidiotsonegamejam/gmtk26/src/game"
type C2SCreateGamePacket struct {
Public bool `json:"public"`
MaxPlayers uint8 `json:"max_players"`
Seed int64 `json:"seed"`
}

type C2SJoinGamePacket struct {
Expand Down
2 changes: 1 addition & 1 deletion src/server/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ func (c *Client) handleCreateGamePacket(packet *packets.C2SCreateGamePacket) (pa
return nil, fatalPacketErrorf("handle create game packet: client is not ready")
}

state, err := Lobbies.CreateGame(c, packet.Public, packet.MaxPlayers, packet.Seed)
state, err := Lobbies.CreateGame(c, packet.Public, packet.MaxPlayers)
if err != nil {
return &packets.S2CGameRejectedPacket{
Operation: "create",
Expand Down
21 changes: 17 additions & 4 deletions src/server/lobby_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package server

import (
"crypto/rand"
"encoding/binary"
"errors"
"fmt"
"log"
Expand Down Expand Up @@ -66,7 +67,7 @@ func NewLobbyManager() *LobbyManager {
}
}

func (m *LobbyManager) CreateGame(client *Client, public bool, maxPlayers uint8, seed int64) (game.Game, error) {
func (m *LobbyManager) CreateGame(client *Client, public bool, maxPlayers uint8) (game.Game, error) {
if maxPlayers < 1 || maxPlayers > 4 {
return game.Game{}, fmt.Errorf("max players must be 1, 2, 3, or 4")
}
Expand Down Expand Up @@ -104,9 +105,6 @@ func (m *LobbyManager) CreateGame(client *Client, public bool, maxPlayers uint8,
Multiplayer: maxPlayers > 1,
MaxPlayers: maxPlayers,
Round: 1,
Map: game.Map{
Seed: seed,
},
}
state.Factions[0].Player = playerPointer(player)
for i := int(maxPlayers); i < len(state.Factions); i++ {
Expand Down Expand Up @@ -215,6 +213,13 @@ func (m *LobbyManager) StartGame(client *Client) error {
return ErrNotEnoughPlayers
}

seed, err := newGameSeed()
if err != nil {
m.mu.Unlock()
return err
}
active.state.Map.Seed = seed

delete(m.lobbies, gameID)
delete(m.gameCodes, active.state.GameCode)
for _, c := range active.clients {
Expand Down Expand Up @@ -459,6 +464,14 @@ func (m *LobbyManager) newGameCodeLocked() (string, error) {
return "", fmt.Errorf("generate game code: no unique code available")
}

func newGameSeed() (int64, error) {
var bytes [8]byte
if _, err := rand.Read(bytes[:]); err != nil {
return 0, fmt.Errorf("generate game seed: %w", err)
}
return int64(binary.LittleEndian.Uint64(bytes[:])), nil
}

func availableFaction(state game.Game) int {
factionCount := min(int(state.MaxPlayers), len(state.Factions))
for i := range factionCount {
Expand Down
18 changes: 13 additions & 5 deletions src/ui/game_world_element.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,17 @@ func GameWorld() *GameWorldElement {

type GameWorldElement struct {
BaseElement[*GameWorldElement]
Map game.Map
Renderer render.WorldRenderer
Map game.Map
Renderer render.WorldRenderer
pendingFocus *game.Hex
}

func (el *GameWorldElement) prepare() {
if el.Map.Grid == nil {
el.Map.Generate()
}
el.Renderer.Init(&el.Map)
if el.pendingFocus != nil {
el.Renderer.FocusOnHex(*el.pendingFocus)
el.pendingFocus = nil
}
}

func (el *GameWorldElement) update(deltaNano int64) {
Expand All @@ -36,3 +38,9 @@ func (el *GameWorldElement) update(deltaNano int64) {
func (el *GameWorldElement) draw() {
el.Renderer.Draw(&el.Map)
}

// FocusOnHex defers camera focus until prepare has initialized the renderer.
// This matters when the world stays hidden throughout the lobby.
func (el *GameWorldElement) FocusOnHex(hex game.Hex) {
el.pendingFocus = &hex
}
93 changes: 15 additions & 78 deletions src/ui/screens/game_creation_screen.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package screens

import (
"fmt"
"hash/fnv"
"math/rand"
"strconv"

gameNet "github.com/threeidiotsonegamejam/gmtk26/src/net"
Expand All @@ -28,7 +26,6 @@ var (
creationPublic = true
creationSubmitting bool
creationError string
creationSeedInput *ui.InputElement
)

func OpenSoloGameCreation(previousScreen *ui.ScreenElement) {
Expand All @@ -50,11 +47,11 @@ func RejectGameCreation(message string) {
}

func StartSoloWithDefaults() error {
return startSoloGame(rand.Int63())
return startSoloGame()
}

func HostGameWithDefaults() error {
return sendHostGame(true, 4, rand.Int63())
return sendHostGame(true, 4)
}

func openGameCreation(mode gameCreationMode, previousScreen *ui.ScreenElement) {
Expand All @@ -64,7 +61,6 @@ func openGameCreation(mode gameCreationMode, previousScreen *ui.ScreenElement) {
creationSubmitting = false
creationError = ""
screen := NewGameCreationScreen(previousScreen)
creationSeedInput.SetText(strconv.FormatInt(rand.Int63(), 10))
SetActiveScreen(screen)
}

Expand All @@ -83,37 +79,6 @@ func NewGameCreationScreen(previousScreen *ui.ScreenElement) *ui.ScreenElement {
}
})

creationSeedInput = ui.Input().
WithPlaceholderText("Seed or phrase").
WithMaxTextLength(48).
WithDefaultText("").
WithTextSize(30).
WithPadding(10).
WithSize(vec.Vec2i{X: 360, Y: 54}).
WithAnchors(anchor.Center, anchor.Center).
WithRelativePos(vec.Vec2i{X: -82, Y: -72}).
WithEnabledDynamic(func(el *ui.InputElement) bool {
return !creationSubmitting
}).
WithCallback(func(text string) {
creationError = ""
})

randomSeedButton := ui.Button().
WithText("Random").
WithTextSize(28).
WithPadding(8).
WithSize(vec.Vec2i{X: 150, Y: 54}).
WithAnchors(anchor.Center, anchor.Center).
WithRelativePos(vec.Vec2i{X: 205, Y: -72}).
WithEnabledDynamic(func(el *ui.ButtonElement) bool {
return !creationSubmitting
}).
WithClick(func() {
creationSeedInput.SetText(strconv.FormatInt(rand.Int63(), 10))
creationError = ""
})

playerCountButtons := make([]ui.Element, 0, 3)
for i := range 3 {
count := uint8(i + 2)
Expand All @@ -128,7 +93,7 @@ func NewGameCreationScreen(previousScreen *ui.ScreenElement) *ui.ScreenElement {
WithPadding(8).
WithSize(vec.Vec2i{X: 100, Y: 52}).
WithAnchors(anchor.Center, anchor.Center).
WithRelativePos(vec.Vec2i{X: int32(i-1) * 120, Y: 34}).
WithRelativePos(vec.Vec2i{X: int32(i-1) * 120, Y: -22}).
WithVisibleDynamic(func(el *ui.ButtonElement) bool {
return hostMode()
}).
Expand All @@ -152,7 +117,7 @@ func NewGameCreationScreen(previousScreen *ui.ScreenElement) *ui.ScreenElement {
WithPadding(8).
WithSize(vec.Vec2i{X: 360, Y: 52}).
WithAnchors(anchor.Center, anchor.Center).
WithRelativePos(vec.Vec2i{X: 0, Y: 104}).
WithRelativePos(vec.Vec2i{X: 0, Y: 48}).
WithVisibleDynamic(func(el *ui.ButtonElement) bool {
return hostMode()
}).
Expand Down Expand Up @@ -182,9 +147,9 @@ func NewGameCreationScreen(previousScreen *ui.ScreenElement) *ui.ScreenElement {
WithAnchors(anchor.Center, anchor.Center).
WithRelativePosDynamic(func(el *ui.ButtonElement) vec.Vec2i {
if hostMode() {
return vec.Vec2i{X: 0, Y: 170}
return vec.Vec2i{X: 0, Y: 114}
}
return vec.Vec2i{X: 0, Y: 82}
return vec.Vec2i{X: 0, Y: 48}
}).
WithEnabledDynamic(func(el *ui.ButtonElement) bool {
return !creationSubmitting && (!hostMode() || hostConnected())
Expand Down Expand Up @@ -212,30 +177,20 @@ func NewGameCreationScreen(previousScreen *ui.ScreenElement) *ui.ScreenElement {
if hostMode() {
return "Choose the rules"
}
return "Choose a world seed before starting"
return "The world is generated when the game starts"
}).
WithTextSize(26).
WithTextColor(uiutil.MenuMutedColor).
WithAnchors(anchor.Center, anchor.Top).
WithRelativePos(vec.Vec2i{X: 0, Y: 122}),
).
AddChild(
ui.Text().
WithText("World Seed").
WithTextSize(25).
WithTextColor(uiutil.MenuMutedColor).
WithAnchors(anchor.Center, anchor.Center).
WithRelativePos(vec.Vec2i{X: 0, Y: -116}),
).
AddChild(creationSeedInput).
AddChild(randomSeedButton).
AddChild(
ui.Text().
WithText("Maximum Players").
WithTextSize(25).
WithTextColor(uiutil.MenuMutedColor).
WithAnchors(anchor.Center, anchor.Center).
WithRelativePos(vec.Vec2i{X: 0, Y: -12}).
WithRelativePos(vec.Vec2i{X: 0, Y: -68}).
WithVisibleDynamic(func(el *ui.TextElement) bool {
return hostMode()
}),
Expand All @@ -246,7 +201,7 @@ func NewGameCreationScreen(previousScreen *ui.ScreenElement) *ui.ScreenElement {
WithTextSize(26).
WithTextColor(uiutil.MenuMutedColor).
WithAnchors(anchor.Center, anchor.Center).
WithRelativePos(vec.Vec2i{X: 0, Y: 10}).
WithRelativePos(vec.Vec2i{X: 0, Y: -18}).
WithVisibleDynamic(func(el *ui.TextElement) bool {
return !hostMode()
}),
Expand Down Expand Up @@ -281,44 +236,39 @@ func NewGameCreationScreen(previousScreen *ui.ScreenElement) *ui.ScreenElement {
}),
).
AddChild(uiutil.MenuVignette()).
WithBack(goBack).
WithExit(func() {
creationSeedInput.Blur()
})
WithBack(goBack)
}

func submitGameCreation() {
seed := gameSeedFromText(creationSeedInput.Value())
creationError = ""

if creationMode == gameCreationSolo {
if err := startSoloGame(seed); err != nil {
if err := startSoloGame(); err != nil {
creationError = capitalizeSentence(err.Error())
return
}
creationSubmitting = true
return
}

if err := sendHostGame(creationPublic, creationMaxPlayers, seed); err != nil {
if err := sendHostGame(creationPublic, creationMaxPlayers); err != nil {
creationError = capitalizeSentence(err.Error())
return
}
creationSubmitting = true
}

func startSoloGame(seed int64) error {
return gameNet.StartLocalGame(seed)
func startSoloGame() error {
return gameNet.StartLocalGame()
}

func sendHostGame(public bool, maxPlayers uint8, seed int64) error {
func sendHostGame(public bool, maxPlayers uint8) error {
if settings.Current.Offline || gameNet.State() != gameNet.ConnectionConnected {
return fmt.Errorf("Connect to the multiplayer server before creating a game")
}
if err := gameNet.Send(&packets.C2SCreateGamePacket{
Public: public,
MaxPlayers: maxPlayers,
Seed: seed,
}); err != nil {
return err
}
Expand All @@ -334,16 +284,3 @@ func gameCreationStatus() string {
}
return ""
}

func gameSeedFromText(text string) int64 {
if text == "" || text == "0" {
return 0
}
if seed, err := strconv.ParseInt(text, 10, 64); err == nil {
return seed
}

hash := fnv.New64a()
_, _ = hash.Write([]byte(text))
return int64(hash.Sum64())
}
Loading