mirror of
https://github.com/Terncode/pixel.horse.git
synced 2026-09-24 21:55:52 +02:00
Archive commit
This commit is contained in:
@@ -0,0 +1,802 @@
|
||||
import { compact, escapeRegExp, repeat } from 'lodash';
|
||||
import { createBinaryReader, readUint8, readUint32, readUint16 } from 'ag-sockets/dist/browser';
|
||||
import { decodeString } from 'ag-sockets/dist/utf8';
|
||||
import {
|
||||
Entity, Region, Pony, EntityState, PonyOptions, MessageType, isNonIgnorableMessage, EntityPlayerState,
|
||||
EntityOrPonyOptions, isPublicMessage, FakeEntity, Action, WorldMap, DoAction, UpdateType, DecodedUpdate,
|
||||
PonyData, WorldStateFlags, FriendStatusData, FriendStatusFlags, isWhisper, isWhisperTo,
|
||||
} from '../common/interfaces';
|
||||
import { bitmask, setFlag, findById, distance, hasFlag, distanceXY, invalidEnum, removeItem } from '../common/utils';
|
||||
import { isChatVisible } from '../common/camera';
|
||||
import { createRegion, worldToRegionX, worldToRegionY } from '../common/region';
|
||||
import { createAnEntity, poof, poof2 } from '../common/entities';
|
||||
import { getPonyState, setPonyState, isPonyFlying, addChatBubble, isHidden } from '../common/entityUtils';
|
||||
import {
|
||||
isPony, createPony, setPonyExpression, updatePonyInfo, updatePonyHold, doPonyAction, hasHeadAnimation,
|
||||
setHeadAnimation,
|
||||
doBoopPonyAction
|
||||
} from '../common/pony';
|
||||
import { PonyTownGame } from './game';
|
||||
import { setupPlayer, savePlayerPosition } from './sec';
|
||||
import { PONY_INFO_KEY, FLY_DELAY, isChatlogRangeUnlimited, SECOND, PONY_TYPE } from '../common/constants';
|
||||
import { getSaysTime, containsCyrillic } from './clientUtils';
|
||||
import { dismissSays } from '../graphics/graphicsUtils';
|
||||
import { decodeUpdate, readOneUpdate } from '../common/encoders/updateDecoder';
|
||||
import { updateEntityVelocity } from '../common/entityUtils';
|
||||
import { decodePonyInfo } from '../common/compressPony';
|
||||
import { mockPaletteManager } from '../common/ponyInfo';
|
||||
import { yawn, laugh, sneeze } from './ponyAnimations';
|
||||
import {
|
||||
findEntityById, getRegionGlobal, setTile, removeEntity, addEntity, removeEntityDirectly, setRegion,
|
||||
addEntityToMapRegion, switchEntityRegion, getRegionUnsafe, addOrRemoveFromEntityList,
|
||||
} from '../common/worldMap';
|
||||
import { isSelected } from './gameUtils';
|
||||
import { compareFriends } from '../components/services/model';
|
||||
import { canCollideWith } from '../common/collision';
|
||||
import { hasDrawLight, hasLightSprite } from './draw';
|
||||
|
||||
function log(message: string) {
|
||||
if (DEVELOPMENT && !TESTS) {
|
||||
console.error(message);
|
||||
}
|
||||
}
|
||||
|
||||
function handleAddEntity(game: PonyTownGame, region: Region, update: DecodedUpdate, initial: boolean) {
|
||||
const {
|
||||
id, type = 0, x = 0, y = 0, vx = 0, vy = 0, state = 0, playerState = 0, options = {},
|
||||
name, filterName, info, crc = 0, action // , expression
|
||||
} = update;
|
||||
|
||||
const filteredName = filterEntityName(game, name, filterName);
|
||||
const entity = createEntityOrPony(game, type, id, x, y, options, crc, filteredName, info, state);
|
||||
|
||||
entity.id = id;
|
||||
entity.x = x;
|
||||
entity.y = y;
|
||||
entity.vx = vx;
|
||||
entity.vy = vy;
|
||||
entity.playerState = playerState || EntityPlayerState.None;
|
||||
|
||||
addEntityToMapRegion(game.map, region, entity);
|
||||
|
||||
if (isPony(entity)) {
|
||||
if (id === game.playerId) {
|
||||
game.apply(() => setupPlayer(game, entity));
|
||||
}
|
||||
|
||||
if (isSelected(game, id)) {
|
||||
game.select(entity);
|
||||
}
|
||||
|
||||
if (game.whisperTo && game.whisperTo.id === id) {
|
||||
game.whisperTo = entity;
|
||||
}
|
||||
|
||||
if (!initial) {
|
||||
game.onPonyAddOrUpdate.next(entity);
|
||||
}
|
||||
}
|
||||
|
||||
if (action !== undefined) {
|
||||
handleAction(game, id, action);
|
||||
}
|
||||
}
|
||||
|
||||
export function handleUpdateEntity(game: PonyTownGame, update: DecodedUpdate) {
|
||||
const {
|
||||
id, x, y, vx, vy, state, playerState, expression, options, switchRegion,
|
||||
name, filterName, info, crc = 0, action
|
||||
} = update;
|
||||
|
||||
const filteredName = filterEntityName(game, name, filterName);
|
||||
const entity = findEntityByIdInGame(game, id);
|
||||
|
||||
if (entity) {
|
||||
const isPlayer = id === game.playerId;
|
||||
|
||||
if (x !== undefined && y !== undefined) {
|
||||
if (switchRegion) {
|
||||
if (DEVELOPMENT && isPlayer && !getRegionGlobal(game.map, x, y)) {
|
||||
console.error(`Switching player to unsubscribed region`);
|
||||
}
|
||||
|
||||
switchEntityRegion(game.map, entity, x, y);
|
||||
}
|
||||
|
||||
if (!isPlayer) {
|
||||
// if (DEVELOPMENT && isPony(entity)) {
|
||||
// const dx = entity.x - x;
|
||||
// const dy = entity.y - y;
|
||||
// console.log(`adjust x: [${num(dx)}] (${ms(dx)}) y: [${num(dy)}] (${ms(dy)})`);
|
||||
// }
|
||||
|
||||
entity.x = x;
|
||||
entity.y = y;
|
||||
updateEntityVelocity(game.map, entity, vx, vy);
|
||||
|
||||
if (canCollideWith(entity)) {
|
||||
const rx = worldToRegionX(entity.x, game.map);
|
||||
const ry = worldToRegionY(entity.y, game.map);
|
||||
|
||||
for (let y = -1; y <= 1; y++) {
|
||||
for (let x = -1; x <= 1; x++) {
|
||||
const region = getRegionUnsafe(game.map, rx + x, ry + y);
|
||||
|
||||
if (region) {
|
||||
region.colliderDirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (distanceXY(entity.x, entity.y, x, y) > 8) {
|
||||
log(`Fixing player position (${entity.x}, ${entity.y}) => (${x}, ${y})`);
|
||||
entity.x = x;
|
||||
entity.y = y;
|
||||
savePlayerPosition();
|
||||
}
|
||||
}
|
||||
|
||||
if (state !== undefined) {
|
||||
updateEntityStateInternal(game, entity, state);
|
||||
}
|
||||
|
||||
if (playerState !== undefined) {
|
||||
updateEntityPlayerStateInternal(game, entity, playerState);
|
||||
}
|
||||
|
||||
if (expression !== undefined && isPony(entity)) {
|
||||
setPonyExpression(entity, expression);
|
||||
}
|
||||
|
||||
if (options != null) {
|
||||
updateEntityOptionsInternal(entity, options, game);
|
||||
}
|
||||
|
||||
if (filteredName !== undefined && !isPlayer) {
|
||||
entity.name = filteredName;
|
||||
}
|
||||
|
||||
if (info !== undefined && !isPlayer) {
|
||||
const ponyInfo = bitmask(info, PONY_INFO_KEY);
|
||||
|
||||
if (entity.fake) {
|
||||
(entity as Pony).palettePonyInfo = decodePonyInfo(ponyInfo, mockPaletteManager);
|
||||
} else {
|
||||
updatePonyInfoWithPoof(game, entity, ponyInfo, crc);
|
||||
}
|
||||
}
|
||||
|
||||
if (action !== undefined) {
|
||||
handleAction(game, id, action);
|
||||
}
|
||||
|
||||
applyIfSelected(game, id);
|
||||
} else {
|
||||
log(`handleUpdateEntity: missing entity: ${id}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function handleUpdatePonies(game: PonyTownGame, ponies: PonyData[]) {
|
||||
for (const [id, options = {}, name, info, playerState, nameBad] of ponies) {
|
||||
const decodedName = name && decodeString(name) || undefined;
|
||||
const filteredName = filterEntityName(game, decodedName, nameBad);
|
||||
const decodedInfo = info ? bitmask(info, PONY_INFO_KEY) : '';
|
||||
const pony = createPonyEntity(game, id, options, filteredName, decodedInfo, EntityState.None);
|
||||
pony.playerState = playerState;
|
||||
game.fallbackPonies.set(pony.id, pony);
|
||||
}
|
||||
|
||||
const missing = game.party && game.party.members.filter(p => !p.pony);
|
||||
|
||||
if (missing && missing.length) {
|
||||
game.apply(() => missing.forEach(p => p.pony = game.fallbackPonies.get(p.id)));
|
||||
}
|
||||
}
|
||||
|
||||
function createPonyEntity(
|
||||
game: PonyTownGame, id: number, options: PonyOptions, name: string | undefined, info: string | Uint8Array,
|
||||
state: EntityState
|
||||
) {
|
||||
if (!game.webgl) {
|
||||
throw new Error('WebGL not initialized');
|
||||
}
|
||||
|
||||
const pony = createPony(id, state, info, game.webgl.palettes.defaultPalette, game.paletteManager);
|
||||
|
||||
if (name) {
|
||||
pony.name = name;
|
||||
}
|
||||
|
||||
updateEntityOptionsInternal(pony, options, game);
|
||||
|
||||
// bypass name/info filtering for player pony
|
||||
if (id === game.playerId) {
|
||||
if (game.playerName) {
|
||||
pony.name = game.playerName;
|
||||
}
|
||||
|
||||
if (game.playerInfo) {
|
||||
pony.crc = game.playerCRC;
|
||||
updatePonyInfo(pony, game.playerInfo, game.applyChanges);
|
||||
}
|
||||
}
|
||||
|
||||
return pony;
|
||||
}
|
||||
|
||||
function updateEntityStateInternal(game: PonyTownGame, entity: Entity, state: EntityState) {
|
||||
if (entity === game.player) {
|
||||
const right = game.rightOverride;
|
||||
const headTurned = game.headTurnedOverride;
|
||||
const stateOverride = game.stateOverride;
|
||||
|
||||
if (right !== undefined) {
|
||||
state = setFlag(state, EntityState.FacingRight, right);
|
||||
game.rightOverride = undefined;
|
||||
}
|
||||
|
||||
if (headTurned !== undefined) {
|
||||
state = setFlag(state, EntityState.HeadTurned, headTurned);
|
||||
game.headTurnedOverride = undefined;
|
||||
}
|
||||
|
||||
if (stateOverride !== undefined) {
|
||||
if (stateOverride !== getPonyState(state)) {
|
||||
state = setPonyState(state, stateOverride);
|
||||
}
|
||||
|
||||
game.stateOverride = undefined;
|
||||
}
|
||||
|
||||
game.onActionsUpdate.next();
|
||||
}
|
||||
|
||||
const wasPonyFlying = isPonyFlying(entity);
|
||||
const hadLight = hasDrawLight(entity);
|
||||
const hadLightSprite = hasLightSprite(entity);
|
||||
|
||||
entity.state = state;
|
||||
|
||||
if (!wasPonyFlying && isPonyFlying(entity) && isPony(entity)) {
|
||||
entity.inTheAirDelay = FLY_DELAY;
|
||||
}
|
||||
|
||||
const hasLight = hasDrawLight(entity);
|
||||
const hasLightSprite1 = hasLightSprite(entity);
|
||||
|
||||
addOrRemoveFromEntityList(game.map.entitiesLight, entity, hadLight, hasLight);
|
||||
addOrRemoveFromEntityList(game.map.entitiesLightSprite, entity, hadLightSprite, hasLightSprite1);
|
||||
}
|
||||
|
||||
function updateEntityPlayerStateInternal(game: PonyTownGame, entity: Entity, playerState: EntityPlayerState) {
|
||||
if (!entity.fake && !isHidden(entity) && hasFlag(playerState, EntityPlayerState.Hidden)) {
|
||||
playEffect(game, entity, poof.type);
|
||||
|
||||
if (isSelected(game, entity.id)) {
|
||||
game.select(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
entity.playerState = playerState;
|
||||
}
|
||||
|
||||
function findEntityByIdInGame(game: PonyTownGame, id: number) {
|
||||
let entity = findEntityById(game.map, id);
|
||||
|
||||
if (!entity && isSelected(game, id)) {
|
||||
entity = game.selected;
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
function applyIfSelected(game: PonyTownGame, id: number) {
|
||||
if (isSelected(game, id)) {
|
||||
game.applyChanges();
|
||||
}
|
||||
}
|
||||
|
||||
export function handleUpdates(game: PonyTownGame, updates: Uint8Array) {
|
||||
const reader = createBinaryReader(updates);
|
||||
|
||||
while (reader.offset < reader.view.byteLength) {
|
||||
const type = readUint8(reader) as UpdateType;
|
||||
|
||||
switch (type) {
|
||||
case UpdateType.None:
|
||||
log(`handleUpdates (none)`);
|
||||
break;
|
||||
case UpdateType.AddEntity: {
|
||||
const update = readOneUpdate(reader)!;
|
||||
const { x = 0, y = 0 } = update;
|
||||
const region = getRegionGlobal(game.map, x, y);
|
||||
|
||||
if (region) {
|
||||
handleAddEntity(game, region, update, false);
|
||||
} else {
|
||||
log(`handleUpdates (add): missing region at ${x} ${y}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UpdateType.UpdateEntity: {
|
||||
const update = readOneUpdate(reader)!;
|
||||
handleUpdateEntity(game, update);
|
||||
break;
|
||||
}
|
||||
case UpdateType.RemoveEntity: {
|
||||
const id = readUint32(reader);
|
||||
handleRemoveEntity(game, id);
|
||||
break;
|
||||
}
|
||||
case UpdateType.UpdateTile: {
|
||||
const x = readUint16(reader);
|
||||
const y = readUint16(reader);
|
||||
const type = readUint8(reader);
|
||||
setTile(game.map, x, y, type);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
invalidEnum(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function updatePonyInfoWithPoof(game: PonyTownGame, entity: Entity, info: string | Uint8Array, crc: number) {
|
||||
const update = (pony: Pony) => {
|
||||
pony.crc = crc;
|
||||
updatePonyInfo(pony, info, game.applyChanges);
|
||||
game.onPonyAddOrUpdate.next(pony);
|
||||
};
|
||||
|
||||
if (entity && isPony(entity)) {
|
||||
if (isHidden(entity)) {
|
||||
update(entity);
|
||||
} else {
|
||||
playEffect(game, entity, poof2.type);
|
||||
setTimeout(() => update(entity), 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function handleRemoveEntity(game: PonyTownGame, id: number) {
|
||||
const entity = findEntityById(game.map, id);
|
||||
|
||||
if (entity) {
|
||||
removeEntity(game.map, entity);
|
||||
} else {
|
||||
log(`handleRemoveEntity: Missing entity: ${id}`);
|
||||
}
|
||||
|
||||
if (id === game.playerId) {
|
||||
log(`handleRemoveEntity: Removing player`);
|
||||
}
|
||||
|
||||
if (entity && entity.type === PONY_TYPE) {
|
||||
playEffect(game, entity, poof.type);
|
||||
}
|
||||
|
||||
if (isSelected(game, id)) {
|
||||
setTimeout(() => {
|
||||
if (isSelected(game, id)) {
|
||||
game.select(undefined);
|
||||
}
|
||||
}, 15 * SECOND);
|
||||
}
|
||||
}
|
||||
|
||||
function findPonyById(map: WorldMap, id: number) {
|
||||
const entity = findEntityById(map, id);
|
||||
return entity && isPony(entity) ? entity : undefined;
|
||||
}
|
||||
|
||||
export function handleAction(game: PonyTownGame, id: number, action: Action) {
|
||||
const pony = findPonyById(game.map, id);
|
||||
|
||||
if (pony) {
|
||||
switch (action) {
|
||||
case Action.Boop:
|
||||
doBoopPonyAction(game, pony);
|
||||
break;
|
||||
case Action.HoldPoof:
|
||||
doPonyAction(pony, DoAction.HoldPoof);
|
||||
break;
|
||||
case Action.Yawn:
|
||||
if (!hasHeadAnimation(pony)) {
|
||||
setHeadAnimation(pony, yawn);
|
||||
}
|
||||
break;
|
||||
case Action.Laugh:
|
||||
if (!hasHeadAnimation(pony)) {
|
||||
setHeadAnimation(pony, laugh);
|
||||
}
|
||||
break;
|
||||
case Action.Sneeze:
|
||||
if (!hasHeadAnimation(pony)) {
|
||||
setHeadAnimation(pony, sneeze);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
log(`handleAction: Invalid action: ${action}`);
|
||||
}
|
||||
} else {
|
||||
log(`handleAction: Missing entity: ${id}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function playEffect(game: PonyTownGame, target: Entity, type: number) {
|
||||
if (isHidden(target))
|
||||
return;
|
||||
|
||||
try {
|
||||
const entity = createAnEntity(type, 0, target.x, target.y, {}, game.paletteManager, game);
|
||||
addEntity(game.map, entity);
|
||||
setTimeout(() => removeEntityDirectly(game.map, entity), 1000);
|
||||
} catch (e) {
|
||||
DEVELOPMENT && console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
export function findEntityOrMockByAnyMeans(game: PonyTownGame, id: number) {
|
||||
if (!id) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let entity: Entity | FakeEntity | undefined = findEntityById(game.map, id);
|
||||
|
||||
if (!entity && game.party) {
|
||||
const member = findById(game.party.members, id);
|
||||
entity = member && member.pony;
|
||||
}
|
||||
|
||||
if (!entity) {
|
||||
const friend = game.model.friends && game.model.friends.find(f => f.entityId === id);
|
||||
|
||||
if (friend) {
|
||||
entity = { fake: true, type: PONY_TYPE, id: friend.entityId, name: friend.actualName, crc: friend.crc };
|
||||
}
|
||||
}
|
||||
|
||||
if (!entity) {
|
||||
entity = game.findEntityFromChatLog(id);
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
export function findBestEntityByName(game: PonyTownGame, name: string): Entity | FakeEntity | undefined {
|
||||
const regex = new RegExp(`^${escapeRegExp(name)}$`, 'i');
|
||||
|
||||
if (game.model.friends) {
|
||||
for (const friend of game.model.friends) {
|
||||
if (friend.online && friend.entityId && regex.test(friend.actualName)) {
|
||||
return { fake: true, type: PONY_TYPE, id: friend.entityId, name: friend.actualName, crc: friend.crc };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let result: Entity | FakeEntity | undefined = undefined;
|
||||
|
||||
if (game.player) {
|
||||
for (const entity of game.map.entities) {
|
||||
if (entity.type === PONY_TYPE && entity.id !== game.playerId && !isHidden(entity) && entity.name && regex.test(entity.name)) {
|
||||
if (!result || (distance(game.player, entity) < distance(game.player, result))) {
|
||||
result = entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
result = game.findEntityFromChatLogByName(name);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function findMatchingEntityNames(game: PonyTownGame, match: string): string[] {
|
||||
const result: string[] = [];
|
||||
const ids = new Set<number>();
|
||||
const regex = new RegExp(`^${escapeRegExp(match)}`, 'i');
|
||||
|
||||
if (game.model.friends) {
|
||||
for (const friend of game.model.friends) {
|
||||
if (friend.online && friend.entityId && friend.actualName && regex.test(friend.actualName)) {
|
||||
ids.add(friend.entityId);
|
||||
result.push(friend.actualName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const entity of game.map.entities) {
|
||||
if (
|
||||
entity.type === PONY_TYPE &&
|
||||
entity.id !== game.playerId &&
|
||||
entity.name &&
|
||||
!isHidden(entity) &&
|
||||
regex.test(entity.name) &&
|
||||
!ids.has(entity.id)
|
||||
) {
|
||||
result.push(entity.name);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
let cachedFilter: string | undefined = undefined;
|
||||
let cachedRegex: RegExp | undefined = undefined;
|
||||
|
||||
export function containsFilteredWords(message: string, filter: string | undefined) {
|
||||
if (cachedFilter !== filter) {
|
||||
if (filter) {
|
||||
const words = compact(filter.replace(/[,]/g, ' ').split(/[\r\n\t ]+/g).map(x => x.trim()));
|
||||
cachedRegex = new RegExp(`(^| )(${words.map(escapeRegExp).join('|')})($| )`, 'i');
|
||||
} else {
|
||||
cachedRegex = undefined;
|
||||
}
|
||||
|
||||
cachedFilter = filter;
|
||||
}
|
||||
|
||||
return cachedRegex && cachedRegex.test(message);
|
||||
}
|
||||
|
||||
export function handleSays(game: PonyTownGame, id: number, message: string, type: MessageType) {
|
||||
const entity = findEntityOrMockByAnyMeans(game, id);
|
||||
|
||||
if (entity) {
|
||||
handleSay(game, entity, message, type);
|
||||
} else {
|
||||
DEVELOPMENT && console.warn('incomplete say');
|
||||
game.incompleteSays.push({ id, message, type, time: Date.now() });
|
||||
game.send(server => server.actionParam2(Action.RequestEntityInfo, id));
|
||||
}
|
||||
}
|
||||
|
||||
function isFriendEntityId(game: PonyTownGame, id: number) {
|
||||
if (game.model.friends) {
|
||||
for (const friend of game.model.friends) {
|
||||
if (friend.entityId === id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function shouldShowChatMessage(game: PonyTownGame, entity: Entity | FakeEntity, message: string, type: MessageType): boolean {
|
||||
if (entity === game.player)
|
||||
return true;
|
||||
|
||||
if (isWhisperTo(type))
|
||||
return true;
|
||||
|
||||
if (isWhisper(type) && isFriendEntityId(game, entity.id))
|
||||
return true;
|
||||
|
||||
if (isPublicMessage(type) && !entity.fake && !isChatVisible(game.camera, entity))
|
||||
return false;
|
||||
|
||||
if (isNonIgnorableMessage(type))
|
||||
return true;
|
||||
|
||||
if (game.settings.account.filterCyrillic && containsCyrillic(message))
|
||||
return false;
|
||||
|
||||
if (game.settings.account.ignorePublicChat && isPublicMessage(type))
|
||||
return false;
|
||||
|
||||
if (isWhisper(type) && game.settings.account.ignoreNonFriendWhispers)
|
||||
return false;
|
||||
|
||||
if (containsFilteredWords(message, game.settings.account.filterWords))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function isChatInRange(entity: Entity, player: Entity | undefined, range: number | undefined) {
|
||||
return player === undefined || isChatlogRangeUnlimited(range) || distance(entity, player) < range!;
|
||||
}
|
||||
|
||||
function shouldShowChatMessageInChatlog(game: PonyTownGame, entity: Entity | FakeEntity, type: MessageType) {
|
||||
if (entity.type !== PONY_TYPE)
|
||||
return false;
|
||||
|
||||
if (entity.fake)
|
||||
return true;
|
||||
|
||||
if (!isPublicMessage(type))
|
||||
return true;
|
||||
|
||||
if (!isChatInRange(entity, game.player, game.settings.account.chatlogRange))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function handleSay(game: PonyTownGame, entity: Entity | FakeEntity, message: string, type: MessageType) {
|
||||
if (!shouldShowChatMessage(game, entity, message, type))
|
||||
return;
|
||||
|
||||
if (type === MessageType.Dismiss || message === '.') {
|
||||
if (!entity.fake && entity.says) {
|
||||
dismissSays(entity.says);
|
||||
}
|
||||
} else {
|
||||
const bubbleEntity = isWhisperTo(type) ? game.player : entity;
|
||||
|
||||
if (bubbleEntity && !bubbleEntity.fake && game.map.entitiesById.has(bubbleEntity.id)) {
|
||||
const total = getSaysTime(message);
|
||||
addChatBubble(game.map, bubbleEntity, { message, type, total, timer: total, created: Date.now() });
|
||||
}
|
||||
|
||||
if (isWhisper(type)) {
|
||||
const friend = game.model.friends && game.model.friends.find(f => f.entityId === entity.id);
|
||||
game.lastWhisperFrom = { entityId: entity.id, accountId: friend && friend.accountId };
|
||||
}
|
||||
|
||||
if (shouldShowChatMessageInChatlog(game, entity, type)) {
|
||||
const { id, name = '', crc } = entity;
|
||||
game.messageQueue.push({ id, crc, name, message, type });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function handleEntityInfo(game: PonyTownGame, id: number, name: string, crc: number, nameBad: boolean) {
|
||||
name = filterEntityName(game, name, nameBad)!;
|
||||
|
||||
for (let i = 0; i < game.incompleteSays.length;) {
|
||||
const say = game.incompleteSays[i];
|
||||
|
||||
if (say.id === id) {
|
||||
game.incompleteSays.splice(i, 1);
|
||||
const entity: FakeEntity = { fake: true, type: PONY_TYPE, id, name, crc };
|
||||
handleSay(game, entity, say.message, say.type);
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function subscribeRegion(game: PonyTownGame, data: Uint8Array) {
|
||||
const { x, y, updates, tileData } = decodeUpdate(data);
|
||||
const region = createRegion(x, y, tileData!);
|
||||
const initial = !game.loaded;
|
||||
setRegion(game.map, x, y, region);
|
||||
|
||||
for (const update of updates) {
|
||||
handleAddEntity(game, region, update, initial);
|
||||
}
|
||||
}
|
||||
|
||||
export function filterEntityName({ settings, worldFlags }: PonyTownGame, name: string | undefined, nameBad: boolean) {
|
||||
if (name && nameBad && (settings.account.filterSwearWords || hasFlag(worldFlags, WorldStateFlags.Safe))) {
|
||||
return repeat('*', name.length);
|
||||
} else if (name && containsFilteredWords(name, settings.account.filterWords)) {
|
||||
return repeat('?', name.length);
|
||||
} else {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
function createEntityOrPony(
|
||||
game: PonyTownGame, type: number, id: number, x: number, y: number, options: EntityOrPonyOptions,
|
||||
crc: number, name: string | undefined, info: Uint8Array | undefined, state: EntityState
|
||||
): Entity {
|
||||
if (type === PONY_TYPE) {
|
||||
const entity = createPonyEntity(game, id, options, name, info ? bitmask(info, PONY_INFO_KEY) : '', state);
|
||||
const member = game.party && game.party.members.find(p => p.id === id);
|
||||
entity.crc = crc;
|
||||
|
||||
if (member) {
|
||||
game.apply(() => member.pony = entity);
|
||||
}
|
||||
|
||||
if (isSelected(game, id)) {
|
||||
game.select(entity);
|
||||
}
|
||||
|
||||
return entity;
|
||||
} else {
|
||||
const entity = createAnEntity(type, id, x, y, options, game.paletteManager, game);
|
||||
|
||||
entity.state = state;
|
||||
|
||||
if (name) {
|
||||
entity.name = name;
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
||||
function updateEntityOptionsInternal(entity: Entity, options: Partial<EntityOrPonyOptions>, game: PonyTownGame) {
|
||||
Object.assign(entity, options);
|
||||
|
||||
if (isPony(entity) && 'hold' in options) {
|
||||
updatePonyHold(entity, game);
|
||||
}
|
||||
}
|
||||
|
||||
export function handleUpdateFriends(game: PonyTownGame, friends: FriendStatusData[], removeMissing: boolean) {
|
||||
if (!game.model.friends)
|
||||
return;
|
||||
|
||||
for (const { accountId, accountName, status, entityId, name, info, crc, nameBad = false } of friends) {
|
||||
let friend = game.model.friends.find(f => f.accountId === accountId);
|
||||
|
||||
if (hasFlag(status, FriendStatusFlags.Remove)) {
|
||||
if (friend) {
|
||||
removeItem(game.model.friends, friend);
|
||||
}
|
||||
} else {
|
||||
if (!friend) {
|
||||
friend = {
|
||||
accountId,
|
||||
accountName: '',
|
||||
online: false,
|
||||
name: undefined,
|
||||
nameBad: false,
|
||||
pony: undefined,
|
||||
entityId: 0,
|
||||
crc: 0,
|
||||
ponyInfo: undefined,
|
||||
actualName: '',
|
||||
};
|
||||
|
||||
game.model.friends.push(friend);
|
||||
}
|
||||
|
||||
friend.online = hasFlag(status, FriendStatusFlags.Online);
|
||||
|
||||
if (accountName !== undefined) {
|
||||
friend.accountName = accountName;
|
||||
}
|
||||
|
||||
if (entityId !== undefined) {
|
||||
if (game.lastWhisperFrom && game.lastWhisperFrom.accountId === friend.accountId) {
|
||||
game.lastWhisperFrom.entityId = entityId;
|
||||
}
|
||||
|
||||
game.onEntityIdUpdate.next({ old: friend.entityId, new: entityId });
|
||||
|
||||
friend.entityId = entityId;
|
||||
}
|
||||
|
||||
if (name !== undefined) {
|
||||
friend.name = name;
|
||||
friend.nameBad = nameBad;
|
||||
friend.actualName = filterEntityName(game, name, nameBad) || '';
|
||||
}
|
||||
|
||||
if (crc !== undefined) {
|
||||
friend.crc = crc;
|
||||
}
|
||||
|
||||
if (info !== undefined) {
|
||||
friend.pony = info;
|
||||
friend.ponyInfo = decodePonyInfo(info, mockPaletteManager);
|
||||
}
|
||||
|
||||
if (friend.entityId && game.whisperTo && game.whisperTo.id === friend.entityId) {
|
||||
game.whisperTo.name = friend.actualName;
|
||||
game.whisperTo.crc = friend.crc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (removeMissing) {
|
||||
for (let i = game.model.friends.length - 1; i >= 0; i--) {
|
||||
if (!friends.find(f => f.accountId === game.model.friends![i].accountId)) {
|
||||
game.model.friends.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
DEVELOPMENT && console.log('Refreshing friend list');
|
||||
}
|
||||
|
||||
game.model.friends.sort(compareFriends);
|
||||
game.apply(() => { });
|
||||
}
|
||||
Reference in New Issue
Block a user