mirror of
https://codeberg.org/demostf/frontend.git
synced 2026-06-03 18:24:12 +02:00
kill search
This commit is contained in:
parent
78d8d4eb9f
commit
72c4b6ee08
10 changed files with 413 additions and 117 deletions
|
|
@ -15,6 +15,8 @@ import {getMapBoundaries} from "./MapBoundries";
|
|||
import {createEffect, createSignal, untrack} from "solid-js";
|
||||
import {Session, StateUpdate} from "./Session";
|
||||
import {DemoHead} from "../../header";
|
||||
import {EventSearch} from "./EventSearch";
|
||||
import {Event} from "./Data/Parser";
|
||||
|
||||
export interface AnalyseProps {
|
||||
header: DemoHead;
|
||||
|
|
@ -37,10 +39,13 @@ export const Analyser = (props: AnalyseProps) => {
|
|||
const [clients, setClients] = createSignal<number>(0);
|
||||
const [helpOpen, setHelpOpen] = createSignal<boolean>(false);
|
||||
const [gotoOpen, setGotoOpen] = createSignal<boolean>(false);
|
||||
const [searchOpen, setSearchOpen] = createSignal<boolean>(false);
|
||||
const [search, setSearch] = createSignal<string>('');
|
||||
const [gotoInput, setGotoInput] = createSignal<number>(0);
|
||||
const closeDialogs = () => {
|
||||
setHelpOpen(false);
|
||||
setGotoOpen(false);
|
||||
setSearchOpen(false);
|
||||
};
|
||||
|
||||
createEffect(() => {
|
||||
|
|
@ -48,34 +53,45 @@ export const Analyser = (props: AnalyseProps) => {
|
|||
|
||||
untrack(() => {
|
||||
if (e) {
|
||||
if (e.key === '.') {
|
||||
seek(1);
|
||||
e.preventDefault();
|
||||
}
|
||||
if (e.key === ',') {
|
||||
seek(-1);
|
||||
e.preventDefault();
|
||||
}
|
||||
if (e.key === 'ArrowRight') {
|
||||
seek(15);
|
||||
e.preventDefault();
|
||||
}
|
||||
if (e.key === 'ArrowLeft') {
|
||||
seek(-15);
|
||||
e.preventDefault();
|
||||
}
|
||||
if (e.key === ' ') {
|
||||
togglePlay();
|
||||
e.preventDefault();
|
||||
}
|
||||
if (e.key === '?') {
|
||||
setHelpOpen(true);
|
||||
setGotoOpen(false);
|
||||
e.preventDefault();
|
||||
const dialogOpen = searchOpen() | gotoOpen();
|
||||
if (!dialogOpen) {
|
||||
if (e.key === '.') {
|
||||
seek(1);
|
||||
e.preventDefault();
|
||||
}
|
||||
if (e.key === ',') {
|
||||
seek(-1);
|
||||
e.preventDefault();
|
||||
}
|
||||
if (e.key === 'ArrowRight') {
|
||||
seek(15);
|
||||
e.preventDefault();
|
||||
}
|
||||
if (e.key === 'ArrowLeft') {
|
||||
seek(-15);
|
||||
e.preventDefault();
|
||||
}
|
||||
if (e.key === ' ') {
|
||||
togglePlay();
|
||||
e.preventDefault();
|
||||
}
|
||||
if (e.key === '?') {
|
||||
setHelpOpen(true);
|
||||
setGotoOpen(false);
|
||||
setSearchOpen(false);
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
if (!inShared && e.getModifierState("Control") && e.key === 'g') {
|
||||
setHelpOpen(false);
|
||||
setGotoOpen(true);
|
||||
setSearchOpen(false);
|
||||
e.preventDefault();
|
||||
}
|
||||
if (!inShared && e.getModifierState("Control") && e.key === 'f') {
|
||||
setHelpOpen(false);
|
||||
setGotoOpen(false);
|
||||
setSearchOpen(true);
|
||||
e.preventDefault();
|
||||
}
|
||||
if (e.key === 'Escape') {
|
||||
|
|
@ -213,6 +229,7 @@ export const Analyser = (props: AnalyseProps) => {
|
|||
const buildings = () => parser.getBuildingsAtTick(tick());
|
||||
const projectiles = () => parser.getProjectilesAtTick(tick());
|
||||
const kills = parser.getKills();
|
||||
const events = parser.getEvents();
|
||||
const playButtonText = () => (playing()) ? '⏸' : '▶️';
|
||||
const inShared = session && !session.isOwner();
|
||||
const isShared = () => sessionName() !== '';
|
||||
|
|
@ -263,7 +280,7 @@ export const Analyser = (props: AnalyseProps) => {
|
|||
})}
|
||||
disabled={inShared}/>
|
||||
</div>
|
||||
<Modal class="help" isOpen={helpOpen()} onCloseRequest={() => setHelpOpen(false)}
|
||||
<Modal isOpen={helpOpen()} onCloseRequest={() => setHelpOpen(false)}
|
||||
closeOnOutsideClick={true} overlayClass="modal-overlay" contentClass="modal-content">
|
||||
<h4>Keyboard Shortcuts</h4>
|
||||
<table class="shortcuts">
|
||||
|
|
@ -310,7 +327,7 @@ export const Analyser = (props: AnalyseProps) => {
|
|||
</tbody>
|
||||
</table>
|
||||
</Modal>
|
||||
<Modal class="goto" isOpen={gotoOpen()} onCloseRequest={() => setGotoOpen(false)}
|
||||
<Modal isOpen={gotoOpen()} onCloseRequest={() => setGotoOpen(false)}
|
||||
closeOnOutsideClick={true} overlayClass="modal-overlay" contentClass="modal-content">
|
||||
<h4>Goto Tick</h4>
|
||||
<form use:formSubmit={gotoTickSubmitted} class="goto">
|
||||
|
|
@ -319,6 +336,16 @@ export const Analyser = (props: AnalyseProps) => {
|
|||
ref={autofocus} autofocus type="text" inputmode="numeric" min={0} max={lastTick - 1}/>
|
||||
</form>
|
||||
</Modal>
|
||||
<Modal isOpen={searchOpen()} onCloseRequest={() => setSearchOpen(false)}
|
||||
closeOnOutsideClick={true} overlayClass="modal-overlay" contentClass="modal-content">
|
||||
<EventSearch
|
||||
players={players()}
|
||||
search={search()}
|
||||
onSearch={setSearch}
|
||||
events={events}
|
||||
onSelect={(event: Event) => setTickNow(event.tick)}
|
||||
></EventSearch>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,13 @@
|
|||
import {BuildingState, Kill, ParsedDemo, PlayerState, ProjectileState, ProjectileType, WorldBoundaries} from "./Parser";
|
||||
import {
|
||||
BuildingState,
|
||||
Event,
|
||||
Kill,
|
||||
ParsedDemo,
|
||||
PlayerState,
|
||||
ProjectileState,
|
||||
ProjectileType,
|
||||
WorldBoundaries
|
||||
} from "./Parser";
|
||||
|
||||
function getCacheBuster(): string {
|
||||
const url = document.querySelector('script[src*="viewer"]').attributes.src.value;
|
||||
|
|
@ -33,7 +42,18 @@ export class AsyncParser {
|
|||
const cachedData: ParsedDemo = event.data.demo;
|
||||
console.log(`packed data: ${(cachedData.data.length / (1024 * 1024)).toFixed(1)}MB`);
|
||||
this.world = cachedData.world;
|
||||
this.demo = new ParsedDemo(cachedData.playerCount, cachedData.buildingCount, cachedData.projectileCount, cachedData.world, cachedData.header, cachedData.data, cachedData.kills, cachedData.playerInfo, cachedData.tickCount);
|
||||
this.demo = new ParsedDemo(
|
||||
cachedData.playerCount,
|
||||
cachedData.buildingCount,
|
||||
cachedData.projectileCount,
|
||||
cachedData.world,
|
||||
cachedData.header,
|
||||
cachedData.data,
|
||||
cachedData.kills,
|
||||
cachedData.playerInfo,
|
||||
cachedData.events,
|
||||
cachedData.tickCount
|
||||
);
|
||||
resolve(this.demo);
|
||||
}
|
||||
}
|
||||
|
|
@ -76,4 +96,8 @@ export class AsyncParser {
|
|||
getKills(): Kill[] {
|
||||
return this.demo.kills
|
||||
}
|
||||
|
||||
getEvents(): Event[] {
|
||||
return this.demo.events
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,6 +54,14 @@ export async function parseDemo(bytes: Uint8Array, progressCallback: (progress:
|
|||
let map = get_map(state);
|
||||
let data = get_data(state);
|
||||
|
||||
let events = kills.map((kill: Kill) => {
|
||||
return {
|
||||
tick: kill.tick,
|
||||
type: "kill",
|
||||
kill
|
||||
} as Event
|
||||
});
|
||||
|
||||
return new ParsedDemo(
|
||||
playerCount,
|
||||
buildingCount,
|
||||
|
|
@ -75,6 +83,7 @@ export async function parseDemo(bytes: Uint8Array, progressCallback: (progress:
|
|||
data,
|
||||
kills,
|
||||
playerInfo,
|
||||
events,
|
||||
tickCount,
|
||||
);
|
||||
}
|
||||
|
|
@ -206,8 +215,20 @@ export class ParsedDemo {
|
|||
public readonly tickCount: number;
|
||||
public readonly kills: Kill[];
|
||||
public readonly playerInfo: PlayerInfo[];
|
||||
public readonly events: Event[];
|
||||
|
||||
constructor(playerCount: number, buildingCount: number, projectileCount: number, world: WorldBoundaries, header: Header, data: Uint8Array, kills: Kill[], playerInfo: PlayerInfo[], tickCount: number) {
|
||||
constructor(
|
||||
playerCount: number,
|
||||
buildingCount: number,
|
||||
projectileCount: number,
|
||||
world: WorldBoundaries,
|
||||
header: Header,
|
||||
data: Uint8Array,
|
||||
kills: Kill[],
|
||||
playerInfo: PlayerInfo[],
|
||||
events: Event[],
|
||||
tickCount: number
|
||||
) {
|
||||
this.playerCount = playerCount;
|
||||
this.buildingCount = buildingCount;
|
||||
this.projectileCount = projectileCount;
|
||||
|
|
@ -216,6 +237,7 @@ export class ParsedDemo {
|
|||
this.data = data;
|
||||
this.kills = kills;
|
||||
this.playerInfo = playerInfo;
|
||||
this.events = events;
|
||||
this.tickCount = tickCount;
|
||||
}
|
||||
|
||||
|
|
@ -309,3 +331,11 @@ function unpackProjectile(bytes: Uint8Array, base: number, world: WorldBoundarie
|
|||
projectileType,
|
||||
}
|
||||
}
|
||||
|
||||
export type KillEvent = {
|
||||
type: "kill";
|
||||
tick: number,
|
||||
kill: Kill,
|
||||
}
|
||||
|
||||
export type Event = KillEvent;
|
||||
137
script/viewer/Analyse/EventSearch.tsx
Normal file
137
script/viewer/Analyse/EventSearch.tsx
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
import {Event, Kill, PlayerState} from "./Data/Parser";
|
||||
import {createEffect, createSignal, For, Show, untrack} from "solid-js";
|
||||
import {getPlayer, KillIcon, PlayerName, PlayerNames} from "./Render/KillFeed";
|
||||
import {autofocus} from "@solid-primitives/autofocus";
|
||||
import {useKeyDownEvent} from "@solid-primitives/keyboard";
|
||||
|
||||
export interface EventSearchProps {
|
||||
events: Event[];
|
||||
players: PlayerState[];
|
||||
onSearch: (string) => void;
|
||||
search: string;
|
||||
selectedEvent: number;
|
||||
onSelect: (event: Event) => void;
|
||||
}
|
||||
|
||||
export function EventSearch(props: EventSearchProps) {
|
||||
const keyEvent = useKeyDownEvent();
|
||||
const [selected, setSelected] = createSignal<number>(0);
|
||||
|
||||
const events = () => filterEvents(props.events, props.players, props.search);
|
||||
|
||||
createEffect(() => {
|
||||
const e = keyEvent();
|
||||
|
||||
untrack(() => {
|
||||
if (e) {
|
||||
const seekSelected = (offset) => {
|
||||
const target = Math.max(0, Math.min(selected() + offset, events().length - 1));
|
||||
setSelected(target);
|
||||
}
|
||||
if (e.key === 'ArrowUp') {
|
||||
seekSelected(-1);
|
||||
e.preventDefault();
|
||||
}
|
||||
if (e.key === 'ArrowDown') {
|
||||
seekSelected(1);
|
||||
e.preventDefault();
|
||||
}
|
||||
if (e.key === 'Enter') {
|
||||
props.onSelect(events()[selected()]);
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return (<div class="event-search">
|
||||
<input type="text" ref={autofocus} autofocus value={props.search}
|
||||
onInput={(e) => props.onSearch(e.target.value)}/>
|
||||
<table class="event-list">
|
||||
<For each={events()}>{(event, i) =>
|
||||
<EventView event={event} highlighted={i() == selected()} players={props.players}/>
|
||||
}</For>
|
||||
</table>
|
||||
</div>)
|
||||
}
|
||||
|
||||
interface EventViewProps {
|
||||
event: Event;
|
||||
highlighted: boolean,
|
||||
players: PlayerState[];
|
||||
}
|
||||
|
||||
function EventView(props: EventViewProps) {
|
||||
let row;
|
||||
const highlightClass = () => ` ${props.highlighted ? 'highlighted' : ''}`;
|
||||
createEffect(() => {
|
||||
if (props.highlighted) {
|
||||
row.scrollIntoView(false);
|
||||
}
|
||||
})
|
||||
return (
|
||||
<tr ref={row} class={props.event.type + highlightClass()}>
|
||||
<Show when={props.event.type == "kill"}>
|
||||
<KillView kill={props.event.kill} players={props.players}/>
|
||||
</Show>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
interface KillViewProps {
|
||||
kill: Kill;
|
||||
players: PlayerState[];
|
||||
}
|
||||
|
||||
function KillView(props: KillViewProps) {
|
||||
const attacker = getPlayer(props.players, props.kill.attacker);
|
||||
const assister = getPlayer(props.players, props.kill.assister);
|
||||
let victim = getPlayer(props.players, props.kill.victim);
|
||||
|
||||
return <>
|
||||
<td class="kill-source">
|
||||
<PlayerNames players={[attacker, assister]}/>
|
||||
</td>
|
||||
<td class="kill-icon">
|
||||
<KillIcon kill={props.kill}/>
|
||||
</td>
|
||||
<td className="kill-target">
|
||||
<PlayerName player={victim}/>
|
||||
</td>
|
||||
</>
|
||||
}
|
||||
|
||||
function filterEvents(events: Event[], players: PlayerState[], query: string): Event[] {
|
||||
if (query === '') {
|
||||
return events;
|
||||
}
|
||||
query = query.toLowerCase();
|
||||
let filteredEvents = [].concat(events);
|
||||
let queryParts = query.split(' ').filter(part => part.length > 0);
|
||||
for (const queryPart of queryParts) {
|
||||
const playersForPart = findPlayers(players, queryPart);
|
||||
filteredEvents = filteredEvents.filter(event => eventMatches(event, playersForPart, queryPart));
|
||||
}
|
||||
return filteredEvents;
|
||||
}
|
||||
|
||||
function findPlayers(players: PlayerState[], queryPart: string): number[] {
|
||||
return players.flatMap(player => {
|
||||
if (player.info.name.toLowerCase().includes(queryPart)) {
|
||||
return [player.info.userId]
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function eventMatches(event: Event, matchedPlayers: number[], queryPart: string): boolean {
|
||||
if (event.type === "kill") {
|
||||
const kill = event.kill;
|
||||
return matchedPlayers.includes(kill.attacker) ||
|
||||
matchedPlayers.includes(kill.assister) ||
|
||||
matchedPlayers.includes(kill.victim);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,68 +1,89 @@
|
|||
import {Kill, PlayerState} from "../Data/Parser";
|
||||
import {killAlias} from "./killAlias";
|
||||
import {For, Show} from "solid-js";
|
||||
|
||||
export interface KillFeedProps {
|
||||
kills: Kill[],
|
||||
tick: number;
|
||||
players: PlayerState[];
|
||||
kills: Kill[],
|
||||
tick: number;
|
||||
players: PlayerState[];
|
||||
}
|
||||
|
||||
export function KillFeed(props: KillFeedProps) {
|
||||
const {kills} = props;
|
||||
const relevantKills = () => kills.filter(kill => kill.tick <= props.tick && kill.tick >= (props.tick - 30 * 10));
|
||||
const {kills} = props;
|
||||
const relevantKills = () => kills.filter(kill => kill.tick <= props.tick && kill.tick >= (props.tick - 30 * 10));
|
||||
|
||||
return <div class="killfeed">
|
||||
<For each={relevantKills()}>{(kill) =>
|
||||
<KillFeedItem kill={kill} players={props.players}/>
|
||||
}</For>
|
||||
</div>
|
||||
return <ul class="killfeed">
|
||||
<For each={relevantKills()}>{(kill) =>
|
||||
<KillFeedItem kill={kill} players={props.players}/>
|
||||
}</For>
|
||||
</ul>
|
||||
}
|
||||
|
||||
const teamMap = {
|
||||
0: 'unknown',
|
||||
2: 'red',
|
||||
3: 'blue'
|
||||
0: 'unknown',
|
||||
2: 'red',
|
||||
3: 'blue'
|
||||
};
|
||||
|
||||
export function KillFeedItem({kill, players}: { kill: Kill, players: PlayerState[] }) {
|
||||
const alias = killAlias[kill.weapon] ? killAlias[kill.weapon] : kill.weapon;
|
||||
const attacker = getPlayer(players, kill.attacker);
|
||||
const assister = getPlayer(players, kill.assister);
|
||||
let victim = getPlayer(players, kill.victim);
|
||||
let killIcon;
|
||||
try {
|
||||
killIcon = `/images/kill_icons/${alias}.png`;
|
||||
} catch (e) {
|
||||
console.log(alias);
|
||||
killIcon = `/images/kill_icons/skull.png`;
|
||||
}
|
||||
if (!victim) {
|
||||
victim = {
|
||||
team: 0,
|
||||
info: {
|
||||
name: 'Missing player'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return <div class="kill">
|
||||
{(attacker && kill.attacker !== kill.victim) ?
|
||||
<span class={"player " + teamMap[attacker.team]}>
|
||||
{attacker.info.name}
|
||||
</span> : ''}
|
||||
{(assister && kill.assister !== kill.victim) ?
|
||||
<span class={teamMap[assister.team]}>﹢</span> : ''}
|
||||
{(assister && kill.assister !== kill.victim) ?
|
||||
(<span class={"player " + teamMap[assister.team]}>
|
||||
{assister.info.name}
|
||||
</span>) : ''}
|
||||
<img src={killIcon} class={`kill-icon ${kill.weapon}`}/>
|
||||
<span class={"player " + teamMap[victim.team]}>
|
||||
{victim.info.name}
|
||||
</span>
|
||||
</div>
|
||||
interface KillFeedItemProps {
|
||||
kill: Kill;
|
||||
players: PlayerState[];
|
||||
}
|
||||
|
||||
function getPlayer(players: PlayerState[], entityId: number): PlayerState {
|
||||
return players.find(player => player.info.userId == entityId);
|
||||
export function KillFeedItem(props: KillFeedItemProps) {
|
||||
const attacker = getPlayer(props.players, props.kill.attacker);
|
||||
const assister = getPlayer(props.players, props.kill.assister);
|
||||
let victim = getPlayer(props.players, props.kill.victim);
|
||||
|
||||
return <li class="kill">
|
||||
<PlayerNames players={[attacker, assister]}/>
|
||||
<KillIcon kill={props.kill}/>
|
||||
<PlayerName player={victim}/>
|
||||
</li>
|
||||
}
|
||||
|
||||
interface KillIconProps {
|
||||
kill: Kill;
|
||||
}
|
||||
|
||||
export function KillIcon(props: KillIconProps) {
|
||||
const alias = killAlias[props.kill.weapon] ? killAlias[props.kill.weapon] : props.kill.weapon;
|
||||
let killIcon;
|
||||
try {
|
||||
killIcon = `/images/kill_icons/${alias}.png`;
|
||||
} catch (e) {
|
||||
console.log(alias);
|
||||
killIcon = `/images/kill_icons/skull.png`;
|
||||
}
|
||||
|
||||
return <img src={killIcon} class={`kill-icon ${props.kill.weapon}`}/>
|
||||
}
|
||||
|
||||
interface PlayerNameProps {
|
||||
player: PlayerState | null
|
||||
}
|
||||
|
||||
export function PlayerName(props: PlayerNameProps) {
|
||||
return <Show when={props.player}>
|
||||
<span className={"player " + teamMap[props.player.team]}>
|
||||
{props.player.info.name}
|
||||
</span>
|
||||
</Show>
|
||||
}
|
||||
|
||||
interface PlayerNamesProps {
|
||||
players: (PlayerState | null)[]
|
||||
}
|
||||
|
||||
export function PlayerNames(props: PlayerNamesProps) {
|
||||
return <For each={props.players}>{(player, i) => <>
|
||||
<Show when={i() > 0 && player}>
|
||||
<span className={teamMap[player.team]}>+</span>
|
||||
</Show>
|
||||
<PlayerName player={player}/>
|
||||
</>}</For>
|
||||
}
|
||||
|
||||
export function getPlayer(players: PlayerState[], entityId: number): PlayerState | null {
|
||||
return players.find(player => player.info.userId == entityId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,38 +1,39 @@
|
|||
export const killAlias = {
|
||||
'world': 'skull',
|
||||
'player': 'skull',
|
||||
'telefrag': 'skull',
|
||||
'shotgun_pyro': 'shotgun',
|
||||
'tf_projectile_pipe_remote': 'stickybomb_launcher',
|
||||
'the_classic': 'classic',
|
||||
'tf_projectile_arrow': 'huntsman',
|
||||
'club': 'kukri',
|
||||
'shotgun_primary': 'shotgun',
|
||||
'shotgun_soldier': 'shotgun',
|
||||
'shotgun_hwg': 'shotgun',
|
||||
'pickaxe': 'escape_plan',
|
||||
'tf_projectile_pipe': 'grenade_launcher',
|
||||
'obj_sentrygun': 'sentrygun1',
|
||||
'steel_fists': 'fists_of_steel',
|
||||
'tf_projectile_rocket': 'rocket_launcher',
|
||||
'obj_sentrygun3': 'sentrygun3',
|
||||
'obj_sentrygun2': 'sentrygun2',
|
||||
'worldspawn': 'skull',
|
||||
'nonnonviolent_protest': 'conscientious_objector',
|
||||
'deflect_promode': 'deflect_rocket',
|
||||
'trigger_hurt': 'bleed',
|
||||
'quake_rl': 'original',
|
||||
'wrangler_kill': 'wrangler',
|
||||
'obj_minisentry': 'minisentry',
|
||||
'pistol_scout': 'pistol',
|
||||
'bleed_kill': 'bleed',
|
||||
'maxgun': 'lugermorph',
|
||||
'rocketlauncher_directhit': 'direct_hit',
|
||||
'frontier_kill': 'frontier_justice',
|
||||
'robot_arm_kill': 'gunslinger',
|
||||
'wrench_jag': 'jag',
|
||||
'loose_cannon_explosion': 'loose_cannon',
|
||||
'samrevolver': 'big_kill',
|
||||
'long_heatmaker': 'huo-long_heater',
|
||||
'pep_pistol': 'pistol'
|
||||
'world': 'skull',
|
||||
'player': 'skull',
|
||||
'telefrag': 'skull',
|
||||
'shotgun_pyro': 'shotgun',
|
||||
'tf_projectile_pipe_remote': 'stickybomb_launcher',
|
||||
'the_classic': 'classic',
|
||||
'tf_projectile_arrow': 'huntsman',
|
||||
'club': 'kukri',
|
||||
'shotgun_primary': 'shotgun',
|
||||
'shotgun_soldier': 'shotgun',
|
||||
'shotgun_hwg': 'shotgun',
|
||||
'pickaxe': 'escape_plan',
|
||||
'tf_projectile_pipe': 'grenade_launcher',
|
||||
'obj_sentrygun': 'sentrygun1',
|
||||
'steel_fists': 'fists_of_steel',
|
||||
'tf_projectile_rocket': 'rocket_launcher',
|
||||
'obj_sentrygun3': 'sentrygun3',
|
||||
'obj_sentrygun2': 'sentrygun2',
|
||||
'worldspawn': 'skull',
|
||||
'nonnonviolent_protest': 'conscientious_objector',
|
||||
'deflect_promode': 'deflect_rocket',
|
||||
'trigger_hurt': 'bleed',
|
||||
'quake_rl': 'original',
|
||||
'wrangler_kill': 'wrangler',
|
||||
'obj_minisentry': 'minisentry',
|
||||
'pistol_scout': 'pistol',
|
||||
'bleed_kill': 'bleed',
|
||||
'maxgun': 'lugermorph',
|
||||
'rocketlauncher_directhit': 'direct_hit',
|
||||
'frontier_kill': 'frontier_justice',
|
||||
'robot_arm_kill': 'gunslinger',
|
||||
'wrench_jag': 'jag',
|
||||
'loose_cannon_explosion': 'loose_cannon',
|
||||
'samrevolver': 'big_kill',
|
||||
'long_heatmaker': 'huo-long_heater',
|
||||
'pep_pistol': 'pistol',
|
||||
'ai_flamethrower': 'flamethrower',
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue