Skip to content

Commit

Permalink
feat: implement Papyrus OnHit event & implement NPCs hits (skyrim-mul…
Browse files Browse the repository at this point in the history
  • Loading branch information
Pospelove authored Oct 2, 2023
1 parent 43743e9 commit f8d6560
Show file tree
Hide file tree
Showing 68 changed files with 1,249 additions and 411 deletions.
12 changes: 12 additions & 0 deletions docs/docs_server_configuration_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,18 @@ The name of a localizaiton file in `data/localization` that would be used by `M.
}
```

## enableConsoleCommandsForAll

Enable console commands for all, useful for testing.

```json5
{
// ...
"enableConsoleCommandsForAll": true
// ...
}
```

## sweetPieMinimumPlayersToStart

The minimal amount of players to begin deathmatch. This setting is sweetpie only and does not affect vanilla server. By default is 5.
Expand Down
2 changes: 2 additions & 0 deletions libespm/include/libespm/ACHR.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ class ACHR final : public RecordHeader
{
public:
static constexpr auto kType = "ACHR";

bool StartsDead() const noexcept;
};

static_assert(sizeof(ACHR) == sizeof(RecordHeader));
Expand Down
6 changes: 3 additions & 3 deletions libespm/include/libespm/NPC_.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ class NPC_ final : public RecordHeader
bool isProtected = false;

uint32_t race = 0;
uint16_t healthOffset = 0;
uint16_t magickaOffset = 0;
uint16_t staminaOffset = 0;
int16_t healthOffset = 0;
int16_t magickaOffset = 0;
int16_t staminaOffset = 0;
ObjectBounds objectBounds = {};
};

Expand Down
2 changes: 1 addition & 1 deletion libespm/include/libespm/RecordHeader.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class RecordHeader

uint32_t GetFieldsSizeSum() const noexcept;

private:
protected:
uint32_t flags;
uint32_t id;
uint32_t revision;
Expand Down
12 changes: 12 additions & 0 deletions libespm/src/ACHR.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#include "libespm/ACHR.h"
#include "libespm/RecordHeaderAccess.h"
#include <cstring>

namespace espm {

bool ACHR::StartsDead() const noexcept
{
return this->flags & 0x200;
}

}
6 changes: 3 additions & 3 deletions libespm/src/NPC_.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ NPC_::Data NPC_::GetData(

result.isEssential = !!(flags & 0x02);
result.isProtected = !!(flags & 0x800);
result.magickaOffset = *reinterpret_cast<const uint16_t*>(data + 4);
result.staminaOffset = *reinterpret_cast<const uint16_t*>(data + 6);
result.healthOffset = *reinterpret_cast<const uint16_t*>(data + 20);
result.magickaOffset = *reinterpret_cast<const int16_t*>(data + 4);
result.staminaOffset = *reinterpret_cast<const int16_t*>(data + 6);
result.healthOffset = *reinterpret_cast<const int16_t*>(data + 20);

} else if (!std::memcmp(type, "RNAM", 4)) {
result.race = *reinterpret_cast<const uint32_t*>(data);
Expand Down
6 changes: 6 additions & 0 deletions skymp5-client/src/features/worldCleaner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ function processOneActor(): void {
actor.disableNoWait(true); // Seems to not crash
return;
}

if (actor.isDead()) {
actor.blockActivation(true);
return;
}

actor.disable(false).then(() => {
const ac = Actor.from(Game.getFormEx(actorId));
if (!ac || isInDialogue(ac)) return;
Expand Down
21 changes: 2 additions & 19 deletions skymp5-client/src/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,25 +27,15 @@ export enum MsgType {
OnHit = 17,
DeathStateContainer = 18,
DropItem = 19,
Teleport = 20,
OpenContainer = 21,
}

export interface SetInventory {
type: "setInventory";
inventory: Inventory;
}

export interface OpenContainer {
type: "openContainer";
target: number;
}

export interface Teleport {
type: "teleport";
pos: number[];
rot: number[];
worldOrCell: number;
}

export interface CreateActorMessage {
type: "createActor";
idx: number;
Expand Down Expand Up @@ -74,13 +64,6 @@ export interface UpdatePropertyMessage {
propName: string;
}

export interface DeathStateContainerMessage {
t: MsgType.DeathStateContainer;
tTeleport?: Teleport,
tChangeValues?: ChangeValuesMessage,
tIsDead: UpdatePropertyMessage,
}

export interface SetRaceMenuOpenMessage {
type: "setRaceMenuOpen";
open: boolean;
Expand Down
3 changes: 2 additions & 1 deletion skymp5-client/src/modelSource/msgHandler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as msg from "../messages";
import { ChangeValuesMessage } from "../services/messages/changeValues";
import { DeathStateContainerMessage } from "../services/messages/deathStateContainerMessage";
import { UpdateAnimationMessage } from "../services/messages/updateAnimationMessage";
import { UpdateAppearanceMessage } from "../services/messages/updateAppearanceMessage";
import { UpdateEquipmentMessage } from "../services/messages/updateEquipmentMessage";
Expand All @@ -15,7 +16,7 @@ export interface MsgHandler {
ChangeValues(msg: ChangeValuesMessage): void;
setRaceMenuOpen(msg: msg.SetRaceMenuOpenMessage): void;
customPacket(msg: msg.CustomPacket): void;
DeathStateContainer(msg: msg.DeathStateContainerMessage): void;
DeathStateContainer(msg: DeathStateContainerMessage): void;

handleConnectionAccepted(): void;
handleDisconnect(): void;
Expand Down
88 changes: 58 additions & 30 deletions skymp5-client/src/modelSource/remoteServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { Movement } from '../sync/movement';
import { learnSpells, removeAllSpells } from '../sync/spell';
import { ModelApplyUtils } from '../view/modelApplyUtils';
import {
getObjectReference,
getViewFromStorage,
localIdToRemoteId,
remoteIdToLocalId,
Expand All @@ -55,6 +56,10 @@ import { CustomEventMessage } from '../services/messages/customEventMessage';
import { FinishSpSnippetMessage } from '../services/messages/finishSpSnippetMessage';
import { RagdollService } from '../services/services/ragdollService';
import { UpdateAppearanceMessage } from '../services/messages/updateAppearanceMessage';
import { TeleportMessage } from '../services/messages/teleportMessage';
import { DeathStateContainerMessage } from '../services/messages/deathStateContainerMessage';
import { RespawnNeededError } from '../lib/errors';
import { OpenContainer } from '../services/messages/openContainer';

const onceLoad = (
refrId: number,
Expand Down Expand Up @@ -212,7 +217,7 @@ export class RemoteServer implements MsgHandler, ModelSource, SendTarget {
});
}

openContainer(msg: messages.OpenContainer): void {
OpenContainer(msg: OpenContainer): void {
once('update', async () => {
await Utility.wait(0.1); // Give a chance to update inventory
(
Expand All @@ -232,18 +237,23 @@ export class RemoteServer implements MsgHandler, ModelSource, SendTarget {
});
}

teleport(msg: messages.Teleport): void {
Teleport(msg: TeleportMessage): void {
once('update', () => {
const id = this.getIdManager().getId(msg.idx);
const refr = id === this.getMyActorIndex() ? Game.getPlayer() : getObjectReference(id);
printConsole(
'Teleporting...',
`Teleporting (idx=${msg.idx}) ${refr?.getFormID().toString(16)}...`,
msg.pos,
'cell/world is',
msg.worldOrCell.toString(16),
);
const ragdollService = SpApiInteractor.makeController().lookupListener(RagdollService);
ragdollService.safeRemoveRagdollFromWorld(Game.getPlayer()!, () => {

const refrId = refr?.getFormID();

const removeRagdollCallback = () => {
TESModPlatform.moveRefrToPosition(
Game.getPlayer()!,
ObjectReference.from(Game.getFormEx(refrId || 0)),
Cell.from(Game.getFormEx(msg.worldOrCell)),
WorldSpace.from(Game.getFormEx(msg.worldOrCell)),
msg.pos[0],
Expand All @@ -253,7 +263,14 @@ export class RemoteServer implements MsgHandler, ModelSource, SendTarget {
msg.rot[1],
msg.rot[2],
);
});
};
const actor = Actor.from(refr);
if (actor /*&& actor.getFormID() === 0x14*/) {
ragdollService.safeRemoveRagdollFromWorld(actor, removeRagdollCallback);
}
else {
removeRagdollCallback();
}
});
}

Expand Down Expand Up @@ -347,10 +364,10 @@ export class RemoteServer implements MsgHandler, ModelSource, SendTarget {
Game.getPlayer() as Actor,
msg.equipment
? {
entries: msg.equipment.inv.entries.filter(
(x) => !!Armor.from(Game.getFormEx(x.baseId)),
),
}
entries: msg.equipment.inv.entries.filter(
(x) => !!Armor.from(Game.getFormEx(x.baseId)),
),
}
: { entries: [] },
false,
);
Expand Down Expand Up @@ -410,7 +427,7 @@ export class RemoteServer implements MsgHandler, ModelSource, SendTarget {
const sqr = (x: number) => x * x;
const distance = Math.sqrt(
sqr(pos[0] - msg.transform.pos[0]) +
sqr(pos[1] - msg.transform.pos[1]),
sqr(pos[1] - msg.transform.pos[1]),
);
if (distance < 256) {
break;
Expand Down Expand Up @@ -481,16 +498,16 @@ export class RemoteServer implements MsgHandler, ModelSource, SendTarget {
msg.transform.worldOrCell,
msg.appearance
? {
name: msg.appearance.name,
raceId: msg.appearance.raceId,
face: {
hairColor: msg.appearance.hairColor,
bodySkinColor: msg.appearance.skinColor,
headTextureSetId: msg.appearance.headTextureSetId,
headPartIds: msg.appearance.headpartIds,
presets: msg.appearance.presets,
},
}
name: msg.appearance.name,
raceId: msg.appearance.raceId,
face: {
hairColor: msg.appearance.hairColor,
bodySkinColor: msg.appearance.skinColor,
headTextureSetId: msg.appearance.headTextureSetId,
headPartIds: msg.appearance.headpartIds,
presets: msg.appearance.presets,
},
}
: undefined,
);
once('update', () => {
Expand Down Expand Up @@ -585,7 +602,7 @@ export class RemoteServer implements MsgHandler, ModelSource, SendTarget {
(form as Record<string, unknown>)[msg.propName] = msg.data;
}

DeathStateContainer(msg: messages.DeathStateContainerMessage): void {
DeathStateContainer(msg: DeathStateContainerMessage): void {
once('update', () =>
printConsole(`Received death state: ${JSON.stringify(msg.tIsDead)}`),
);
Expand All @@ -601,7 +618,7 @@ export class RemoteServer implements MsgHandler, ModelSource, SendTarget {
once('update', () => this.UpdateProperty(msg.tIsDead));

if (msg.tTeleport) {
this.teleport(msg.tTeleport);
this.Teleport(msg.tTeleport);
}

const id = this.getIdManager().getId(msg.tIsDead.idx);
Expand All @@ -612,10 +629,19 @@ export class RemoteServer implements MsgHandler, ModelSource, SendTarget {
? Game.getPlayer()!
: Actor.from(Game.getFormEx(remoteIdToLocalId(form.refrId ?? 0)));
if (actor) {
SpApiInteractor.makeController().emitter.emit("applyDeathStateEvent", {
actor: Game.getPlayer()!,
isDead: msg.tIsDead.data as boolean
});
try {
SpApiInteractor.makeController().emitter.emit("applyDeathStateEvent", {
actor: actor,
isDead: msg.tIsDead.data as boolean
});
} catch (e) {
if (e instanceof RespawnNeededError) {
actor.disableNoWait(false);
actor.delete();
} else {
throw e;
}
}
}
});
}
Expand All @@ -627,11 +653,13 @@ export class RemoteServer implements MsgHandler, ModelSource, SendTarget {
loginWithSkympIoCredentials();
}

handleDisconnect(): void {}
handleDisconnect(): void { }

ChangeValues(msg: ChangeValuesMessage): void {
once('update', () => {
const ac = Game.getPlayer();
const id = this.getIdManager().getId(msg.idx);
const refr = id === this.getMyActorIndex() ? Game.getPlayer() : getObjectReference(id);
const ac = Actor.from(refr);
if (!ac) return;
setActorValuePercentage(ac, 'health', msg.data.health);
setActorValuePercentage(ac, 'stamina', msg.data.stamina);
Expand Down Expand Up @@ -728,7 +756,7 @@ export class RemoteServer implements MsgHandler, ModelSource, SendTarget {
storage['eventSourceContexts'] = [];
} else {
storage['eventSourceContexts'].forEach((ctx: Record<string, unknown>) => {
ctx.sendEvent = () => {};
ctx.sendEvent = () => { };
ctx._expired = true;
});
}
Expand Down
4 changes: 4 additions & 0 deletions skymp5-client/src/services/messages/anyMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import { FinishSpSnippetMessage } from "./finishSpSnippetMessage";
import { HitMessage } from "./hitMessage";
import { HostMessage } from "./hostMessage";
import { OnEquipMessage } from "./onEquipMessage";
import { OpenContainer } from "./openContainer";
import { PutItemMessage } from "./putItemMessage";
import { TakeItemMessage } from "./takeItemMessage";
import { TeleportMessage } from "./teleportMessage";
import { UpdateAnimationMessage } from "./updateAnimationMessage";
import { UpdateAppearanceMessage } from "./updateAppearanceMessage";
import { UpdateEquipmentMessage } from "./updateEquipmentMessage";
Expand All @@ -34,3 +36,5 @@ export type AnyMessage = ActivateMessage
| CustomEventMessage
| CustomPacketMessage
| FinishSpSnippetMessage
| TeleportMessage
| OpenContainer
1 change: 1 addition & 0 deletions skymp5-client/src/services/messages/changeValues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ import { ActorValues } from "../../sync/actorvalues";
export interface ChangeValuesMessage {
t: MsgType.ChangeValues;
data: ActorValues;
idx: number;
}
10 changes: 10 additions & 0 deletions skymp5-client/src/services/messages/deathStateContainerMessage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { MsgType, UpdatePropertyMessage } from "../../messages";
import { ChangeValuesMessage } from "./changeValues";
import { TeleportMessage } from "./teleportMessage";

export interface DeathStateContainerMessage {
t: MsgType.DeathStateContainer;
tTeleport?: TeleportMessage,
tChangeValues?: ChangeValuesMessage,
tIsDead: UpdatePropertyMessage,
}
6 changes: 6 additions & 0 deletions skymp5-client/src/services/messages/openContainer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { MsgType } from "../../messages";

export interface OpenContainer {
t: MsgType.OpenContainer;
target: number;
}
9 changes: 9 additions & 0 deletions skymp5-client/src/services/messages/teleportMessage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { MsgType } from "../../messages";

export interface TeleportMessage {
t: MsgType.Teleport;
idx: number;
pos: number[];
rot: number[];
worldOrCell: number;
}
Loading

0 comments on commit f8d6560

Please sign in to comment.