spectator mode

This commit is contained in:
RandomityGuy 2024-06-27 01:57:14 +05:30
parent 57873e6301
commit 3a928a6014
7 changed files with 301 additions and 4 deletions

View file

@ -1,5 +1,6 @@
package src;
import net.Net;
import mis.MisParser;
import h3d.col.Bounds;
import h3d.col.Plane;
@ -28,6 +29,7 @@ import h3d.Vector;
import hxsl.Types.Matrix;
import h3d.scene.Scene;
import src.Gamepad;
import src.MarbleGame;
enum CameraMode {
FreeOrbit;
@ -71,6 +73,9 @@ class CameraController extends Object {
public var finish:Bool = false;
public var overview:Bool = false;
var spectate:Bool = false;
var spectateMarbleIndex:Int = -1;
var overviewCenter:Vector;
var overviewWidth:Vector;
var overviewHeight:Float;
@ -119,6 +124,14 @@ class CameraController extends Object {
#end
}
public function enableSpectate() {
spectate = true;
}
public function stopSpectate() {
spectate = false;
}
public function orbit(mouseX:Float, mouseY:Float, isTouch:Bool = false) {
if (_ignoreCursor) {
_ignoreCursor = false;
@ -229,6 +242,221 @@ class CameraController extends Object {
camera.up.z = 1;
}
function doSpectateCamera(currentTime:Float, dt:Float) {
var camera = level.scene.camera;
var lerpt = Math.pow(0.5, dt / 0.032); // Math.min(1, 1 - Math.pow(0.6, dt / 0.032)); // hxd.Math.min(1, 1 - Math.pow(0.6, dt * 600));
var cameraPitchDelta = (Key.isDown(Settings.controlsSettings.camBackward) ? 1 : 0)
- (Key.isDown(Settings.controlsSettings.camForward) ? 1 : 0)
+ Gamepad.getAxis(Settings.gamepadSettings.cameraYAxis);
if (Settings.gamepadSettings.invertYAxis)
cameraPitchDelta = -cameraPitchDelta;
nextCameraPitch += 0.75 * 5 * cameraPitchDelta * dt * Settings.gamepadSettings.cameraSensitivity;
var cameraYawDelta = (Key.isDown(Settings.controlsSettings.camRight) ? 1 : 0) - (Key.isDown(Settings.controlsSettings.camLeft) ? 1 : 0)
+ Gamepad.getAxis(Settings.gamepadSettings.cameraXAxis);
if (Settings.gamepadSettings.invertXAxis)
cameraYawDelta = -cameraYawDelta;
nextCameraYaw += 0.75 * 5 * cameraYawDelta * dt * Settings.gamepadSettings.cameraSensitivity;
nextCameraPitch = Math.max(-Math.PI / 2 + Math.PI / 4, Math.min(Math.PI / 2 - 0.0001, nextCameraPitch));
CameraYaw = Util.lerp(CameraYaw, nextCameraYaw, lerpt);
CameraPitch = Util.lerp(CameraPitch, nextCameraPitch, lerpt);
CameraPitch = Math.max(-Math.PI / 2 + Math.PI / 4, Math.min(Math.PI / 2 - 0.0001, CameraPitch)); // Util.clamp(CameraPitch, -Math.PI / 12, Math.PI / 2);
function getRotQuat(v1:Vector, v2:Vector) {
function orthogonal(v:Vector) {
var x = Math.abs(v.x);
var y = Math.abs(v.y);
var z = Math.abs(v.z);
var other = x < y ? (x < z ? new Vector(1, 0, 0) : new Vector(0, 0, 1)) : (y < z ? new Vector(0, 1, 0) : new Vector(0, 0, 1));
return v.cross(other);
}
var u = v1.normalized();
var v = v2.normalized();
if (u.multiply(-1).equals(v)) {
var q = new Quat();
var o = orthogonal(u).normalized();
q.x = o.x;
q.y = o.y;
q.z = o.z;
q.w = 0;
return q;
}
var half = u.add(v).normalized();
var q = new Quat();
q.w = u.dot(half);
var vr = u.cross(half);
q.x = vr.x;
q.y = vr.y;
q.z = vr.z;
return q;
}
var orientationQuat = level.getOrientationQuat(currentTime);
if (spectateMarbleIndex == -1) {
var up = new Vector(0, 0, 1);
up.transform(orientationQuat.toMatrix());
var directionVector = new Vector(1, 0, 0);
var q1 = new Quat();
q1.initRotateAxis(0, 1, 0, CameraPitch);
directionVector.transform(q1.toMatrix());
q1.initRotateAxis(0, 0, 1, CameraYaw);
directionVector.transform(q1.toMatrix());
directionVector.transform(orientationQuat.toMatrix());
var dy = Gamepad.getAxis(Settings.gamepadSettings.moveYAxis) * CameraSpeed * dt;
var dx = -Gamepad.getAxis(Settings.gamepadSettings.moveXAxis) * CameraSpeed * dt;
if (Key.isDown(Settings.controlsSettings.forward)) {
dy += CameraSpeed * dt;
}
if (Key.isDown(Settings.controlsSettings.backward)) {
dy -= CameraSpeed * dt;
}
if (Key.isDown(Settings.controlsSettings.left)) {
dx += CameraSpeed * dt;
}
if (Key.isDown(Settings.controlsSettings.right)) {
dx -= CameraSpeed * dt;
}
if (MarbleGame.instance.touchInput.movementInput.pressed) {
dx = -MarbleGame.instance.touchInput.movementInput.value.x * CameraSpeed * dt;
dy = MarbleGame.instance.touchInput.movementInput.value.y * CameraSpeed * dt;
}
if ((!Util.isTouchDevice() && Key.isDown(Settings.controlsSettings.powerup))
|| (Util.isTouchDevice() && MarbleGame.instance.touchInput.powerupButton.pressed)
|| Gamepad.isDown(Settings.gamepadSettings.powerup)) {
dx *= 2;
dy *= 2;
}
if (Key.isPressed(Settings.controlsSettings.blast)
|| (MarbleGame.instance.touchInput.blastbutton.pressed)
|| Gamepad.isPressed(Settings.gamepadSettings.blast)) {
var freeMarbleIndex = -1;
for (i in 0...level.marbles.length) {
var marble = level.marbles[i];
@:privateAccess if ((marble.connection != null && !marble.connection.spectator)) {
freeMarbleIndex = i;
break;
}
}
spectateMarbleIndex = freeMarbleIndex;
return;
}
var sideDir = directionVector.cross(up);
var moveDir = directionVector.multiply(dy).add(sideDir.multiply(dx));
camera.pos.load(camera.pos.add(moveDir));
camera.up = up;
camera.target = camera.pos.add(directionVector);
} else {
if (Key.isPressed(Settings.controlsSettings.left)) {
spectateMarbleIndex = (spectateMarbleIndex - 1 + level.marbles.length) % level.marbles.length;
@:privateAccess while (level.marbles[spectateMarbleIndex].connection == null
|| level.marbles[spectateMarbleIndex].connection.spectator) {
spectateMarbleIndex = (spectateMarbleIndex - 1 + level.marbles.length) % level.marbles.length;
}
}
if (Key.isPressed(Settings.controlsSettings.right)) {
spectateMarbleIndex = (spectateMarbleIndex + 1 + level.marbles.length) % level.marbles.length;
@:privateAccess while (level.marbles[spectateMarbleIndex].connection == null
|| level.marbles[spectateMarbleIndex].connection.spectator) {
spectateMarbleIndex = (spectateMarbleIndex + 1 + level.marbles.length) % level.marbles.length;
}
}
if (Key.isPressed(Settings.controlsSettings.blast)
|| (MarbleGame.instance.touchInput.blastbutton.pressed)
|| Gamepad.isPressed(Settings.gamepadSettings.blast)) {
spectateMarbleIndex = -1;
return;
}
var marblePosition = level.marbles[spectateMarbleIndex].getAbsPos().getPosition();
var up = new Vector(0, 0, 1);
up.transform(orientationQuat.toMatrix());
var directionVector = new Vector(1, 0, 0);
var cameraVerticalTranslation = new Vector(0, 0, 0.3);
var q1 = new Quat();
q1.initRotateAxis(0, 1, 0, CameraPitch);
directionVector.transform(q1.toMatrix());
cameraVerticalTranslation.transform(q1.toMatrix());
q1.initRotateAxis(0, 0, 1, CameraYaw);
directionVector.transform(q1.toMatrix());
cameraVerticalTranslation.transform(q1.toMatrix());
directionVector.transform(orientationQuat.toMatrix());
cameraVerticalTranslation.transform(orientationQuat.toMatrix());
camera.up = up;
camera.pos = marblePosition.sub(directionVector.multiply(CameraDistance));
camera.target = marblePosition.add(cameraVerticalTranslation);
var closeness = 0.1;
var rayCastOrigin = marblePosition.add(level.marbles[spectateMarbleIndex].currentUp.multiply(marble._radius));
var processedShapes = [];
for (i in 0...3) {
var rayCastDirection = camera.pos.sub(rayCastOrigin);
rayCastDirection = rayCastDirection.add(rayCastDirection.normalized().multiply(2));
var rayCastLen = rayCastDirection.length();
var results = level.collisionWorld.rayCast(rayCastOrigin, rayCastDirection.normalized(), rayCastLen);
var firstHit:octree.IOctreeObject.RayIntersectionData = null;
var firstHitDistance = 1e8;
for (result in results) {
if (!processedShapes.contains(result.object)
&& (firstHit == null || (rayCastOrigin.distance(result.point) < firstHitDistance))) {
firstHit = result;
firstHitDistance = rayCastOrigin.distance(result.point);
processedShapes.push(result.object);
}
}
if (firstHit != null) {
if (firstHitDistance < CameraDistance) {
// camera.pos = marblePosition.sub(directionVector.multiply(firstHit.distance * 0.7));
var plane = new Plane(firstHit.normal.x, firstHit.normal.y, firstHit.normal.z, firstHit.point.dot(firstHit.normal));
var normal = firstHit.normal.multiply(-1);
// var position = firstHit.point;
var projected = plane.project(camera.pos.toPoint());
var dist = plane.distance(camera.pos.toPoint());
if (dist >= closeness)
break;
camera.pos = projected.toVector().add(normal.multiply(-closeness));
var forwardVec = marblePosition.sub(camera.pos).normalized();
var rightVec = camera.up.cross(forwardVec).normalized();
var upVec = forwardVec.cross(rightVec);
camera.target = marblePosition.add(upVec.multiply(0.3));
camera.up = upVec;
continue;
}
}
break;
}
}
this.setPosition(camera.pos.x, camera.pos.y, camera.pos.z);
}
public function update(currentTime:Float, dt:Float) {
// camera.position.set(marblePosition.x, marblePosition.y, marblePosition.z).sub(directionVector.clone().multiplyScalar(2.5));
// this.level.scene.camera.target = marblePosition.add(cameraVerticalTranslation);
@ -239,6 +467,11 @@ class CameraController extends Object {
return;
}
if (spectate) {
doSpectateCamera(currentTime, dt);
return;
}
var camera = level.scene.camera;
var lerpt = Math.pow(0.5, dt / 0.032); // Math.min(1, 1 - Math.pow(0.6, dt / 0.032)); // hxd.Math.min(1, 1 - Math.pow(0.6, dt * 600));

View file

@ -1641,6 +1641,16 @@ class Marble extends GameObject {
oldPos = this.collider.transform.getPosition();
prevRot = this.getRotationQuat().clone();
// Handle spectator hacky bullshit
if (Net.isMP) {
if ((connection != null && connection.spectator) || (connection == null && (Net.hostSpectate || Net.clientSpectate))) {
this.collider.transform.setPosition(new Vector(1e8, 1e8, 1e8));
this.collisionWorld.updateTransform(this.collider);
this.setPosition(1e8, 1e8, 1e8);
return;
}
}
// if (this.controllable) {
for (interior in pathedInteriors) {
if (Net.isMP)

View file

@ -540,6 +540,7 @@ class MarbleWorld extends Scheduler {
public function start() {
Console.log("LEVEL START");
restart(this.marble, true);
for (interior in this.interiors)
interior.onLevelStart();
for (shape in this.dtsObjects)

View file

@ -107,6 +107,11 @@ class MPPreGameDlg extends GuiControl {
spectateBtn.vertSizing = Top;
spectateBtn.position = new Vector(190, 394);
spectateBtn.extent = new Vector(127, 33);
spectateBtn.buttonType = Toggle;
spectateBtn.pressedAction = (e) -> {
NetCommands.toggleSpectate(Net.isHost ? 0 : Net.clientId);
updatePlayerList();
}
dialogImg.addChild(spectateBtn);
var serverTitle = new GuiText(markerFelt24);
@ -223,20 +228,23 @@ class MPPreGameDlg extends GuiControl {
if (Net.isHost) {
playerListArr.push({
name: Settings.highscoreName,
ready: Net.lobbyHostReady
ready: Net.lobbyHostReady,
spectate: Net.hostSpectate
});
}
if (Net.isClient) {
playerListArr.push({
name: Settings.highscoreName,
ready: Net.lobbyClientReady
ready: Net.lobbyClientReady,
spectate: Net.clientSpectate
});
}
if (Net.clientIdMap != null) {
for (c => v in Net.clientIdMap) {
playerListArr.push({
name: v.name,
ready: v.lobbyReady
ready: v.lobbyReady,
spectate: v.spectator
});
}
}
@ -249,7 +257,7 @@ class MPPreGameDlg extends GuiControl {
playBtn.disabled = !allReady;
var playerListCompiled = playerListArr.map(player -> player.name);
var playerListCompiled = playerListArr.map(player -> player.spectate ? '[S] ${player.name}' : player.name);
var playerListStateCompiled = playerListArr.map(player -> player.ready ? "[Ready]" : "[Waiting]");
playerListLeft.setTexts(playerListCompiled);
playerListRight.setTexts(playerListStateCompiled);
@ -268,7 +276,9 @@ class MPPreGameDlg extends GuiControl {
// Make everyone un-lobby ready (again!)
for (c in Net.clients) {
c.lobbyReady = false;
c.spectator = false;
}
Net.hostSpectate = false;
Net.lobbyClientReady = false;
Net.lobbyHostReady = false;
if (Net.isHost) {

View file

@ -77,11 +77,13 @@ abstract class GameConnection {
var platform:NetPlatform;
var marbleId:Int;
var marbleCatId:Int;
var spectator:Bool;
function new(id:Int) {
this.id = id;
this.moveManager = new MoveManager(this);
this.lobbyReady = false;
this.spectator = false;
}
public function ready() {
@ -92,6 +94,10 @@ abstract class GameConnection {
lobbyReady = !lobbyReady;
}
public function toggleSpectate() {
spectator = !spectator;
}
public function queueMove(m:NetMove) {
moveManager.queueMove(m);
}

View file

@ -85,6 +85,8 @@ class Net {
public static var lobbyHostReady:Bool;
public static var lobbyClientReady:Bool;
public static var hostReady:Bool;
public static var hostSpectate:Bool;
public static var clientSpectate:Bool;
static var clientIdAllocs:Int = 1;
public static var clientId:Int;
@ -373,6 +375,8 @@ class Net {
Net.lobbyHostReady = false;
Net.lobbyClientReady = false;
Net.hostReady = false;
Net.hostSpectate = false;
Net.clientSpectate = false;
// MultiplayerLevelSelectGui.custSelected = false;
}
if (Net.isHost) {
@ -394,6 +398,8 @@ class Net {
Net.lobbyHostReady = false;
Net.lobbyClientReady = false;
Net.hostReady = false;
Net.hostSpectate = false;
Net.clientSpectate = false;
// MultiplayerLevelSelectGui.custSelected = false;
}
}
@ -629,6 +635,7 @@ class Net {
b.writeByte(v.platform);
b.writeByte(v.marbleId);
b.writeByte(v.marbleCatId);
b.writeByte(v.spectator ? 1 : 0);
var name = v.getName();
b.writeByte(name.length);
for (i in 0...name.length) {
@ -641,6 +648,7 @@ class Net {
b.writeByte(getPlatform());
b.writeByte(Settings.optionsSettings.marbleIndex);
b.writeByte(Settings.optionsSettings.marbleCategoryIndex);
b.writeByte(Net.hostSpectate ? 1 : 0);
var name = Settings.highscoreName;
b.writeByte(name.length);
for (i in 0...name.length) {
@ -752,6 +760,7 @@ class Net {
var platform = input.readByte();
var marble = input.readByte();
var marbleCat = input.readByte();
var cspectator = input.readByte() == 1;
if (id != 0 && id != Net.clientId && !clientIdMap.exists(id)) {
Console.log('Adding ghost connection ${id}');
addGhost(id);
@ -767,9 +776,11 @@ class Net {
clientIdMap[id].setMarbleId(marble, marbleCat);
clientIdMap[id].lobbyReady = cready;
clientIdMap[id].platform = platform;
clientIdMap[id].spectator = cspectator;
}
if (Net.clientId == id) {
Net.lobbyClientReady = cready;
Net.clientSpectate = cspectator;
}
}
if (MarbleGame.canvas.content is MPPlayMissionGui) {

View file

@ -132,6 +132,26 @@ class NetCommands {
}
}
@:rpc(client) public static function toggleSpectate(clientId:Int) {
if (Net.isHost) {
if (clientId == 0)
Net.hostSpectate = !Net.hostSpectate;
else
Net.clientIdMap[clientId].toggleSpectate();
if (MarbleGame.canvas.content is MPPlayMissionGui) {
cast(MarbleGame.canvas.content, MPPlayMissionGui).updateLobbyNames();
}
if (MarbleGame.canvas.children[MarbleGame.canvas.children.length - 1] is MPPreGameDlg) {
cast(MarbleGame.canvas.children[MarbleGame.canvas.children.length - 1], MPPreGameDlg).updatePlayerList();
}
var b = Net.sendPlayerInfosBytes();
for (cc in Net.clients) {
cc.sendBytes(b);
}
}
}
@:rpc(client) public static function clientIsReady(clientId:Int) {
if (Net.isHost) {
if (Net.serverInfo.state == "WAITING") {
@ -214,6 +234,12 @@ class NetCommands {
MarbleGame.instance.world.setCursorLock(true);
MarbleGame.instance.world.marble.camera.stopOverview();
}
if (Net.clientSpectate || Net.hostSpectate) {
MarbleGame.instance.world.marble.camera.enableSpectate();
} else {
MarbleGame.instance.world.marble.camera.stopSpectate();
}
}
}