Sync larger world and smaller player scale
This commit is contained in:
parent
50f277256a
commit
920710e56e
@ -22,9 +22,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
worldSize = 3600.0
|
||||
spawnX = 1200.0
|
||||
spawnY = 1550.0
|
||||
worldSize = 5400.0
|
||||
spawnX = 1800.0
|
||||
spawnY = 2325.0
|
||||
playerMaxHull = 100
|
||||
playerMaxSpeed = 180.0
|
||||
playerReverseSpeed = -55.0
|
||||
@ -36,7 +36,7 @@ const (
|
||||
shotSpeed = 520.0
|
||||
shotLifetime = 900 * time.Millisecond
|
||||
shotDamage = 16
|
||||
shipHitRadius = 44.0
|
||||
shipHitRadius = 34.0
|
||||
respawnDelay = 2200 * time.Millisecond
|
||||
simTick = time.Second / 30
|
||||
snapshotTick = 50 * time.Millisecond
|
||||
@ -229,8 +229,8 @@ func (h *hub) addFakeCaptains(count int) {
|
||||
id := "bot:" + strings.ToLower(strings.ReplaceAll(name, " ", "-"))
|
||||
state := newPlayerState(id, name, 0, 0)
|
||||
phase := math.Pi * 2 * float64(index) / float64(count)
|
||||
state.X = clampFloat(spawnX+math.Cos(phase)*300, 90, worldSize-90)
|
||||
state.Y = clampFloat(spawnY+math.Sin(phase)*300, 90, worldSize-90)
|
||||
state.X = clampFloat(spawnX+math.Cos(phase)*650, 90, worldSize-90)
|
||||
state.Y = clampFloat(spawnY+math.Sin(phase)*650, 90, worldSize-90)
|
||||
state.Angle = normalizeAngle(phase*180/math.Pi + 90)
|
||||
state.Bot = true
|
||||
h.clients[id] = &client{
|
||||
@ -329,8 +329,8 @@ func (h *hub) fireLocked(c *client, side string, now time.Time) bool {
|
||||
projectileID := c.state.ID + ":" + strconv.FormatUint(h.nextProjectile, 10)
|
||||
h.projectiles[projectileID] = projectileState{
|
||||
ID: projectileID, OwnerID: c.state.ID,
|
||||
X: c.state.X + math.Cos(base)*34,
|
||||
Y: c.state.Y + math.Sin(base)*34,
|
||||
X: c.state.X + math.Cos(base)*28,
|
||||
Y: c.state.Y + math.Sin(base)*28,
|
||||
VX: math.Cos(angle)*shotSpeed + shipVX*0.25,
|
||||
VY: math.Sin(angle)*shotSpeed + shipVY*0.25,
|
||||
Damage: shotDamage,
|
||||
@ -428,7 +428,7 @@ func (h *hub) fakeCaptainDecisionLocked(c *client, now time.Time) (inputState, s
|
||||
dx := candidate.state.X - c.state.X
|
||||
dy := candidate.state.Y - c.state.Y
|
||||
distance := math.Hypot(dx, dy)
|
||||
if distance < bestRange {
|
||||
if distance <= 1100 && distance < bestRange {
|
||||
target = candidate
|
||||
bestRange = distance
|
||||
}
|
||||
@ -436,7 +436,7 @@ func (h *hub) fakeCaptainDecisionLocked(c *client, now time.Time) (inputState, s
|
||||
|
||||
if target == nil {
|
||||
phase := float64(c.botIndex)*2.1 + float64(now.UnixMilli()%60000)/60000*math.Pi*2
|
||||
radius := 420.0 + float64(c.botIndex)*75
|
||||
radius := 720.0 + float64(c.botIndex)*110
|
||||
targetX := spawnX + math.Cos(phase)*radius
|
||||
targetY := spawnY + math.Sin(phase)*radius
|
||||
bearing := bearingDegrees(c.state.X, c.state.Y, targetX, targetY)
|
||||
|
||||
@ -25,7 +25,7 @@ func TestTransformSanitizers(t *testing.T) {
|
||||
if got := clampFloat(-5, 0, worldSize); got != 0 {
|
||||
t.Fatalf("clamp low=%v", got)
|
||||
}
|
||||
if got := clampFloat(4000, 0, worldSize); got != worldSize {
|
||||
if got := clampFloat(6000, 0, worldSize); got != worldSize {
|
||||
t.Fatalf("clamp high=%v", got)
|
||||
}
|
||||
if got := clampFloat(math.NaN(), 0, worldSize); got != 0 {
|
||||
@ -190,3 +190,39 @@ func TestFakeCaptainEngagesNearbyHumanWithAuthoritativeBroadside(t *testing.T) {
|
||||
t.Fatal("fake captain input sequence did not advance")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func TestExpandedWorldAndSmallerShipCombatScale(t *testing.T) {
|
||||
if worldSize != 5400 {
|
||||
t.Fatalf("worldSize=%v", worldSize)
|
||||
}
|
||||
if shipHitRadius != 34 {
|
||||
t.Fatalf("shipHitRadius=%v", shipHitRadius)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFakeCaptainDoesNotAggroHumanAcrossWholeWorld(t *testing.T) {
|
||||
h := newHub("TEST", 1)
|
||||
var bot *client
|
||||
for _, candidate := range h.clients {
|
||||
if candidate.state.Bot {
|
||||
bot = candidate
|
||||
break
|
||||
}
|
||||
}
|
||||
human := &client{
|
||||
state: newPlayerState("human-far", "Human", 0, 0),
|
||||
lastFire: map[string]time.Time{},
|
||||
}
|
||||
bot.state.X, bot.state.Y = 500, 500
|
||||
human.state.X, human.state.Y = 5000, 5000
|
||||
h.clients[human.state.ID] = human
|
||||
|
||||
input, fireSide := h.fakeCaptainDecisionLocked(bot, time.Unix(300, 0))
|
||||
if fireSide != "" {
|
||||
t.Fatalf("far human should not trigger broadside: %s", fireSide)
|
||||
}
|
||||
if input.Throttle <= 0 {
|
||||
t.Fatalf("bot should patrol when human is far: %+v", input)
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,7 +71,7 @@ import {
|
||||
merchantRouteEndpoint,
|
||||
} from './world/trade-traffic.js';
|
||||
|
||||
const WORLD_SIZE = 3600;
|
||||
const WORLD_SIZE = 5400;
|
||||
const PLAYER_MAX_SPEED = 180;
|
||||
const PLAYER_REVERSE_SPEED = -55;
|
||||
const PLAYER_ACCELERATION = 92;
|
||||
@ -124,6 +124,8 @@ class GameScene extends Phaser.Scene {
|
||||
seed: querySeed || 'SKYWARD-001',
|
||||
width: WORLD_SIZE,
|
||||
height: WORLD_SIZE,
|
||||
islandCount: 24,
|
||||
portCount: 9,
|
||||
});
|
||||
this.progression = createStarterProgression();
|
||||
this.playerStats = deriveShipStats(this.progression.loadout, this.progression.installedConditions);
|
||||
@ -217,9 +219,9 @@ class GameScene extends Phaser.Scene {
|
||||
|
||||
this.player = this.physics.add.sprite(1200, 1550, 'player-airship', 0);
|
||||
this.player.setPosition(this.world.spawn.x, this.world.spawn.y);
|
||||
this.player.setDepth(6).setCollideWorldBounds(true).setDisplaySize(66, 126);
|
||||
this.player.setDepth(6).setCollideWorldBounds(true).setDisplaySize(52, 100);
|
||||
this.player.body.setAllowGravity(false);
|
||||
this.player.body.setCircle(44, 20, 84);
|
||||
this.player.body.setCircle(34, 30, 94);
|
||||
this.player.setFrame(0);
|
||||
this.createPlayerSmoke();
|
||||
|
||||
@ -325,7 +327,7 @@ class GameScene extends Phaser.Scene {
|
||||
|
||||
this.cameras.main.setBounds(0, 0, this.world.width, this.world.height);
|
||||
this.cameras.main.startFollow(this.player, true, 0.08, 0.08);
|
||||
this.cameras.main.setZoom(1.12);
|
||||
this.cameras.main.setZoom(1.0);
|
||||
|
||||
this.createHud();
|
||||
|
||||
@ -1606,13 +1608,13 @@ class GameScene extends Phaser.Scene {
|
||||
);
|
||||
sprite
|
||||
.setDepth(5.5)
|
||||
.setDisplaySize(60, 114)
|
||||
.setDisplaySize(48, 92)
|
||||
.setTint(0x8cd3ff)
|
||||
.setAlpha(0.86);
|
||||
sprite.play('player-forward');
|
||||
sprite.anims.timeScale = 0.72;
|
||||
|
||||
const label = this.add.text(sprite.x, sprite.y - 70, state.name || 'CAPTAIN', {
|
||||
const label = this.add.text(sprite.x, sprite.y - 56, state.name || 'CAPTAIN', {
|
||||
fontFamily: 'monospace',
|
||||
fontSize: '10px',
|
||||
color: '#bfe6ff',
|
||||
@ -1745,7 +1747,7 @@ class GameScene extends Phaser.Scene {
|
||||
sprite.x = Phaser.Math.Linear(sprite.x, remote.targetX, 0.32);
|
||||
sprite.y = Phaser.Math.Linear(sprite.y, remote.targetY, 0.32);
|
||||
sprite.angle += signedAngleDelta(remote.targetAngle, sprite.angle) * 0.32;
|
||||
remote.label.setPosition(sprite.x, sprite.y - 70);
|
||||
remote.label.setPosition(sprite.x, sprite.y - 56);
|
||||
remote.label.setVisible(window.airshipSettings?.get?.().showPlayerLabels !== false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -187,10 +187,10 @@ const candidatesLength = (islands, ports) => Math.max(
|
||||
|
||||
export const generateWorld = ({
|
||||
seed = 'SKYWARD-001',
|
||||
width = 3600,
|
||||
height = 3600,
|
||||
islandCount = 16,
|
||||
portCount = 7,
|
||||
width = 5400,
|
||||
height = 5400,
|
||||
islandCount = 24,
|
||||
portCount = 9,
|
||||
} = {}) => {
|
||||
const random = createSeededRandom(seed);
|
||||
const regions = createRegions(random, width, height);
|
||||
|
||||
@ -277,8 +277,8 @@ test('player uses the four-frame north-facing sheet as a forward propulsion anim
|
||||
assert.match(source, /import playerAirshipUrl from '.\/assets\/player-airship-4frame\.png\?url'/);
|
||||
assert.match(source, /this\.load\.spritesheet\('player-airship', playerAirshipUrl, \{[\s\S]*?frameWidth: 128,[\s\S]*?frameHeight: 256/);
|
||||
assert.match(source, /this\.physics\.add\.sprite\(1200, 1550, 'player-airship', 0\)/);
|
||||
assert.match(source, /setDisplaySize\(66, 126\)/);
|
||||
assert.match(source, /this\.player\.body\.setCircle\(44, 20, 84\)/);
|
||||
assert.match(source, /setDisplaySize\(52, 100\)/);
|
||||
assert.match(source, /this\.player\.body\.setCircle\(34, 30, 94\)/);
|
||||
assert.match(source, /key: 'player-forward'/);
|
||||
assert.match(source, /generateFrameNumbers\('player-airship', \{ start: 0, end: 3 \}\)/);
|
||||
assert.match(source, /const forwardThrust = clamp\(this\.playerSpeed \/ PLAYER_MAX_SPEED, 0, 1\)/);
|
||||
@ -329,6 +329,28 @@ test('environment physics adds wind, soft island collision, turbulence, and proj
|
||||
assert.match(source, /windSpeed: this\.environmentWind\?\.strength/);
|
||||
});
|
||||
|
||||
test('larger shared world uses smaller player ships and wider fake-captain spacing', async () => {
|
||||
const [game, world, server] = await Promise.all([
|
||||
read('src/game-v4.js'),
|
||||
read('src/world/world-generator.js'),
|
||||
read('server/main.go'),
|
||||
]);
|
||||
|
||||
assert.match(game, /const WORLD_SIZE = 5400/);
|
||||
assert.match(game, /islandCount: 24/);
|
||||
assert.match(game, /portCount: 9/);
|
||||
assert.match(game, /setDisplaySize\(52, 100\)/);
|
||||
assert.match(game, /setCircle\(34, 30, 94\)/);
|
||||
assert.match(game, /setDisplaySize\(48, 92\)/);
|
||||
assert.match(game, /this\.cameras\.main\.setZoom\(1\.0\)/);
|
||||
assert.match(world, /width = 5400/);
|
||||
assert.match(world, /islandCount = 24/);
|
||||
assert.match(server, /worldSize\s+= 5400\.0/);
|
||||
assert.match(server, /shipHitRadius\s+= 34\.0/);
|
||||
assert.match(server, /math\.Cos\(phase\)\*650/);
|
||||
assert.match(server, /distance <= 1100/);
|
||||
});
|
||||
|
||||
test('authoritative multiplayer server seeds fake captains that move and fight like players', async () => {
|
||||
const server = await read('server/main.go');
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user