Compare commits

...
4 Commits
Author SHA1 Message Date
radon 32d98e7524 fix(baseevents): correct killer check 2026-08-24 19:47:17 +02:00
boyandGitHub 94471a3f63 Replace 'GetPlayerByEntityId' with 'NetworkGetPlayerIndexFromPed' (#199)
seems like 'GetPlayerByEntityId' is no longer supported and always return -1 to onPlayerKilled event so, I replace it with 'NetworkGetPlayerIndexFromPed' to make it work properly.
2026-08-24 19:44:45 +02:00
radonandGitHub f798007b6b Merge pull request #226 from chromium-cfx/cleanup
tweak: cleanup server-data
2026-08-24 17:16:32 +02:00
Ethan Wood bfc8ccb8e9 tweak: cleanup server-data 2026-08-24 16:14:13 +01:00
63 changed files with 61 additions and 157371 deletions
+4
View File
@@ -2,3 +2,7 @@
/cache/ /cache/
/resources/\[local\]/ /resources/\[local\]/
/db/ /db/
/logs/
/local-database/
.console-history
.id
@@ -2,7 +2,7 @@ AddEventHandler('gameEventTriggered', function(eventName, args)
if eventName == 'CEventNetworkEntityDamage' then if eventName == 'CEventNetworkEntityDamage' then
local victim = args[1] local victim = args[1]
local culprit = args[2] local culprit = args[2]
local isDead = args[4] == 1 local isDead = args[6] == 1
if isDead then if isDead then
local origCoords = GetEntityCoords(victim) local origCoords = GetEntityCoords(victim)
@@ -3,13 +3,13 @@
version '1.0.0' version '1.0.0'
author 'Cfx.re <[email protected]>' author 'Cfx.re <[email protected]>'
description 'A GTA Online-styled theme for the chat resource.' description 'Example theme for the chat resource.'
repository 'https://github.com/citizenfx/cfx-server-data' repository 'https://github.com/citizenfx/cfx-server-data'
file 'style.css' file 'style.css'
file 'shadow.js' file 'shadow.js'
chat_theme 'gtao' { chat_theme 'example' {
styleSheet = 'style.css', styleSheet = 'style.css',
script = 'shadow.js', script = 'shadow.js',
msgTemplates = { msgTemplates = {
@@ -19,20 +19,9 @@
user-select: none; user-select: none;
} }
@font-face {
font-family: 'Font2';
src: url(https://runtime.fivem.net/temp/ChaletLondonNineteenSixty.otf?a);
}
@font-face {
font-family: 'Font2_cond';
src: url(https://runtime.fivem.net/temp/chaletcomprime-colognesixty-webfont.ttf?a);
}
.msg { .msg {
font-family: Font2, sans-serif; font-family: sans-serif;
color: #fff; color: #fff;
font-size: calc(1.8vh); /* 13px in 720p, calc'd by width */ font-size: calc(1.8vh); /* 13px in 720p, calc'd by width */
filter: url(#svgDropShadowFilter); filter: url(#svgDropShadowFilter);
@@ -47,15 +36,13 @@
} }
.msg > span > span > b { .msg > span > span > b {
font-family: Font2_cond, sans-serif; font-family: sans-serif;
font-weight: normal;
vertical-align: baseline; vertical-align: baseline;
padding-right: 11px; padding-right: 11px;
line-height: 1; line-height: 1;
font-size: calc(2.7vh); font-size: calc(1.8vh);
} }
.msg > span > span > span { .msg > span > span > span {
-4
View File
@@ -1,4 +0,0 @@
node_modules/
.yarn.installed
yarn-error.log
dist/
-1
View File
@@ -1 +0,0 @@
# Chat
-308
View File
@@ -1,308 +0,0 @@
local isRDR = not TerraingridActivate and true or false
local chatInputActive = false
local chatInputActivating = false
local chatLoaded = false
RegisterNetEvent('chatMessage')
RegisterNetEvent('chat:addTemplate')
RegisterNetEvent('chat:addMessage')
RegisterNetEvent('chat:addSuggestion')
RegisterNetEvent('chat:addSuggestions')
RegisterNetEvent('chat:addMode')
RegisterNetEvent('chat:removeMode')
RegisterNetEvent('chat:removeSuggestion')
RegisterNetEvent('chat:clear')
-- internal events
RegisterNetEvent('__cfx_internal:serverPrint')
RegisterNetEvent('_chat:messageEntered')
--deprecated, use chat:addMessage
AddEventHandler('chatMessage', function(author, color, text)
local args = { text }
if author ~= "" then
table.insert(args, 1, author)
end
SendNUIMessage({
type = 'ON_MESSAGE',
message = {
color = color,
multiline = true,
args = args
}
})
end)
AddEventHandler('__cfx_internal:serverPrint', function(msg)
print(msg)
SendNUIMessage({
type = 'ON_MESSAGE',
message = {
templateId = 'print',
multiline = true,
args = { msg },
mode = '_global'
}
})
end)
-- addMessage
local addMessage = function(message)
if type(message) == 'string' then
message = {
args = { message }
}
end
SendNUIMessage({
type = 'ON_MESSAGE',
message = message
})
end
exports('addMessage', addMessage)
AddEventHandler('chat:addMessage', addMessage)
-- addSuggestion
local addSuggestion = function(name, help, params)
SendNUIMessage({
type = 'ON_SUGGESTION_ADD',
suggestion = {
name = name,
help = help,
params = params or nil
}
})
end
exports('addSuggestion', addSuggestion)
AddEventHandler('chat:addSuggestion', addSuggestion)
AddEventHandler('chat:addSuggestions', function(suggestions)
for _, suggestion in ipairs(suggestions) do
SendNUIMessage({
type = 'ON_SUGGESTION_ADD',
suggestion = suggestion
})
end
end)
AddEventHandler('chat:removeSuggestion', function(name)
SendNUIMessage({
type = 'ON_SUGGESTION_REMOVE',
name = name
})
end)
AddEventHandler('chat:addMode', function(mode)
SendNUIMessage({
type = 'ON_MODE_ADD',
mode = mode
})
end)
AddEventHandler('chat:removeMode', function(name)
SendNUIMessage({
type = 'ON_MODE_REMOVE',
name = name
})
end)
AddEventHandler('chat:addTemplate', function(id, html)
SendNUIMessage({
type = 'ON_TEMPLATE_ADD',
template = {
id = id,
html = html
}
})
end)
AddEventHandler('chat:clear', function(name)
SendNUIMessage({
type = 'ON_CLEAR'
})
end)
RegisterNUICallback('chatResult', function(data, cb)
chatInputActive = false
SetNuiFocus(false)
if not data.canceled then
local id = PlayerId()
--deprecated
local r, g, b = 0, 0x99, 255
if data.message:sub(1, 1) == '/' then
ExecuteCommand(data.message:sub(2))
else
TriggerServerEvent('_chat:messageEntered', GetPlayerName(id), { r, g, b }, data.message, data.mode)
end
end
cb('ok')
end)
local function refreshCommands()
if GetRegisteredCommands then
local registeredCommands = GetRegisteredCommands()
local suggestions = {}
for _, command in ipairs(registeredCommands) do
if IsAceAllowed(('command.%s'):format(command.name)) and command.name ~= 'toggleChat' then
table.insert(suggestions, {
name = '/' .. command.name,
help = ''
})
end
end
TriggerEvent('chat:addSuggestions', suggestions)
end
end
local function refreshThemes()
local themes = {}
for resIdx = 0, GetNumResources() - 1 do
local resource = GetResourceByFindIndex(resIdx)
if GetResourceState(resource) == 'started' then
local numThemes = GetNumResourceMetadata(resource, 'chat_theme')
if numThemes > 0 then
local themeName = GetResourceMetadata(resource, 'chat_theme')
local themeData = json.decode(GetResourceMetadata(resource, 'chat_theme_extra') or 'null')
if themeName and themeData then
themeData.baseUrl = 'nui://' .. resource .. '/'
themes[themeName] = themeData
end
end
end
end
SendNUIMessage({
type = 'ON_UPDATE_THEMES',
themes = themes
})
end
AddEventHandler('onClientResourceStart', function(resName)
Wait(500)
refreshCommands()
refreshThemes()
end)
AddEventHandler('onClientResourceStop', function(resName)
Wait(500)
refreshCommands()
refreshThemes()
end)
RegisterNUICallback('loaded', function(data, cb)
TriggerServerEvent('chat:init')
refreshCommands()
refreshThemes()
chatLoaded = true
cb('ok')
end)
local CHAT_HIDE_STATES = {
SHOW_WHEN_ACTIVE = 0,
ALWAYS_SHOW = 1,
ALWAYS_HIDE = 2
}
local kvpEntry = GetResourceKvpString('hideState')
local chatHideState = kvpEntry and tonumber(kvpEntry) or CHAT_HIDE_STATES.SHOW_WHEN_ACTIVE
local isFirstHide = true
if not isRDR then
if RegisterKeyMapping then
RegisterKeyMapping('toggleChat', 'Toggle chat', 'keyboard', 'l')
end
RegisterCommand('toggleChat', function()
if chatHideState == CHAT_HIDE_STATES.SHOW_WHEN_ACTIVE then
chatHideState = CHAT_HIDE_STATES.ALWAYS_SHOW
elseif chatHideState == CHAT_HIDE_STATES.ALWAYS_SHOW then
chatHideState = CHAT_HIDE_STATES.ALWAYS_HIDE
elseif chatHideState == CHAT_HIDE_STATES.ALWAYS_HIDE then
chatHideState = CHAT_HIDE_STATES.SHOW_WHEN_ACTIVE
end
isFirstHide = false
SetResourceKvp('hideState', tostring(chatHideState))
end, false)
end
Citizen.CreateThread(function()
SetTextChatEnabled(false)
SetNuiFocus(false)
local lastChatHideState = -1
local origChatHideState = -1
while true do
Wait(0)
if not chatInputActive then
if IsControlPressed(0, isRDR and `INPUT_MP_TEXT_CHAT_ALL` or 245) --[[ INPUT_MP_TEXT_CHAT_ALL ]] then
chatInputActive = true
chatInputActivating = true
SendNUIMessage({
type = 'ON_OPEN'
})
end
end
if chatInputActivating then
if not IsControlPressed(0, isRDR and `INPUT_MP_TEXT_CHAT_ALL` or 245) then
SetNuiFocus(true)
chatInputActivating = false
end
end
if chatLoaded then
local forceHide = IsScreenFadedOut() or IsPauseMenuActive()
local wasForceHide = false
if chatHideState ~= CHAT_HIDE_STATES.ALWAYS_HIDE then
if forceHide then
origChatHideState = chatHideState
chatHideState = CHAT_HIDE_STATES.ALWAYS_HIDE
end
elseif not forceHide and origChatHideState ~= -1 then
chatHideState = origChatHideState
origChatHideState = -1
wasForceHide = true
end
if chatHideState ~= lastChatHideState then
lastChatHideState = chatHideState
SendNUIMessage({
type = 'ON_SCREEN_STATE_CHANGE',
hideState = chatHideState,
fromUserInteraction = not forceHide and not isFirstHide and not wasForceHide
})
isFirstHide = false
end
end
end
end)
-30
View File
@@ -1,30 +0,0 @@
-- This resource is part of the default Cfx.re asset pack (cfx-server-data)
-- Altering or recreating for local use only is strongly discouraged.
version '1.0.0'
author 'Cfx.re <[email protected]>'
description 'Provides baseline chat functionality using a NUI-based interface.'
repository 'https://github.com/citizenfx/cfx-server-data'
ui_page 'dist/ui.html'
client_script 'cl_chat.lua'
server_script 'sv_chat.lua'
files {
'dist/ui.html',
'dist/index.css',
'html/vendor/*.css',
'html/vendor/fonts/*.woff2',
}
fx_version 'adamant'
games { 'rdr3', 'gta5' }
rdr3_warning 'I acknowledge that this is a prerelease build of RedM, and I am aware my resources *will* become incompatible once RedM ships.'
dependencies {
'yarn',
'webpack'
}
webpack_config 'webpack.config.js'
-467
View File
@@ -1,467 +0,0 @@
import { post } from './utils';
import CONFIG from './config';
import Vue from 'vue';
import Suggestions from './Suggestions.vue';
import MessageV from './Message.vue';
import { Suggestion } from './Suggestions';
export interface Message {
args: string[];
template: string;
params?: { [key: string]: string };
multiline?: boolean;
color?: [ number, number, number ];
templateId?: number;
mode?: string;
modeData?: Mode;
id?: string;
}
export interface ThemeData {
style: string;
styleSheet: string;
baseUrl: string;
script: string;
templates: { [id: string]: string }; // not supported rn
msgTemplates: { [id: string]: string };
}
export interface Mode {
name: string;
displayName: string;
color: string;
hidden?: boolean;
isChannel?: boolean;
isGlobal?: boolean;
}
enum ChatHideStates {
ShowWhenActive = 0,
AlwaysShow = 1,
AlwaysHide = 2,
}
const defaultMode: Mode = {
name: 'all',
displayName: 'All',
color: '#fff'
};
const globalMode: Mode = {
name: '_global',
displayName: 'All',
color: '#fff',
isGlobal: true,
hidden: true
};
export default Vue.extend({
template: "#app_template",
name: "app",
components: {
Suggestions,
MessageV
},
data() {
return {
style: CONFIG.style,
showInput: false,
showWindow: false,
showHideState: false,
hideState: ChatHideStates.ShowWhenActive,
backingSuggestions: [] as Suggestion[],
removedSuggestions: [] as string[],
templates: { ...CONFIG.templates } as { [ key: string ]: string },
message: "",
messages: [] as Message[],
oldMessages: [] as string[],
oldMessagesIndex: -1,
tplBackups: [] as unknown as [ HTMLElement, string ][],
msgTplBackups: [] as unknown as [ string, string ][],
focusTimer: 0,
showWindowTimer: 0,
showHideStateTimer: 0,
listener: (event: MessageEvent) => {},
modes: [defaultMode, globalMode] as Mode[],
modeIdx: 0,
};
},
destroyed() {
clearInterval(this.focusTimer);
window.removeEventListener("message", this.listener);
},
mounted() {
post("http://chat/loaded", JSON.stringify({}));
this.listener = (event: MessageEvent) => {
const item: any = event.data || (<any>event).detail; //'detail' is for debugging via browsers
if (!item || !item.type) {
return;
}
const typeRef = item.type as
'ON_OPEN' | 'ON_SCREEN_STATE_CHANGE' | 'ON_MESSAGE' | 'ON_CLEAR' | 'ON_SUGGESTION_ADD' |
'ON_SUGGESTION_REMOVE' | 'ON_TEMPLATE_ADD' | 'ON_UPDATE_THEMES' | 'ON_MODE_ADD' | 'ON_MODE_REMOVE';
if (this[typeRef]) {
this[typeRef](item);
}
};
window.addEventListener("message", this.listener);
},
watch: {
messages() {
if (this.hideState !== ChatHideStates.AlwaysHide) {
if (this.showWindowTimer) {
clearTimeout(this.showWindowTimer);
}
this.showWindow = true;
this.resetShowWindowTimer();
}
const messagesObj = this.$refs.messages as HTMLDivElement;
this.$nextTick(() => {
messagesObj.scrollTop = messagesObj.scrollHeight;
});
}
},
computed: {
filteredMessages(): Message[] {
return this.messages.filter(
// show messages that are
// - (if the current mode is a channel) global, or in the current mode
// - (if the message is a channel) in the current mode
el => (el.modeData?.isChannel || this.modes[this.modeIdx].isChannel) ?
(el.mode === this.modes[this.modeIdx].name || el.modeData?.isGlobal) :
true
);
},
suggestions(): Suggestion[] {
return this.backingSuggestions.filter(
el => this.removedSuggestions.indexOf(el.name) <= -1
);
},
hideAnimated(): boolean {
return this.hideState !== ChatHideStates.AlwaysHide;
},
modeIdxGet(): number {
return (this.modeIdx >= this.modes.length) ? (this.modes.length - 1) : this.modeIdx;
},
modePrefix(): string {
if (this.modes.length === 2) {
return ``;
}
return this.modes[this.modeIdxGet].displayName;
},
modeColor(): string {
return this.modes[this.modeIdxGet].color;
},
hideStateString(): string {
// TODO: localization
switch (this.hideState) {
case ChatHideStates.AlwaysShow:
return 'Visible';
case ChatHideStates.AlwaysHide:
return 'Hidden';
case ChatHideStates.ShowWhenActive:
return 'When active';
}
}
},
methods: {
ON_SCREEN_STATE_CHANGE({ hideState, fromUserInteraction }: { hideState: ChatHideStates, fromUserInteraction: boolean }) {
this.hideState = hideState;
if (this.hideState === ChatHideStates.AlwaysHide) {
if (!this.showInput) {
this.showWindow = false;
}
} else if (this.hideState === ChatHideStates.AlwaysShow) {
this.showWindow = true;
if (this.showWindowTimer) {
clearTimeout(this.showWindowTimer);
}
} else {
this.resetShowWindowTimer();
}
if (fromUserInteraction) {
this.showHideState = true;
if (this.showHideStateTimer) {
clearTimeout(this.showHideStateTimer);
}
this.showHideStateTimer = window.setTimeout(() => {
this.showHideState = false;
}, 1500);
}
},
ON_OPEN() {
this.showInput = true;
this.showWindow = true;
if (this.showWindowTimer) {
clearTimeout(this.showWindowTimer);
}
this.focusTimer = window.setInterval(() => {
if (this.$refs.input) {
(this.$refs.input as HTMLInputElement).focus();
} else {
clearInterval(this.focusTimer);
}
}, 100);
},
ON_MESSAGE({ message }: { message: Message }) {
message.id = `${new Date().getTime()}${Math.random()}`;
message.modeData = this.modes.find(mode => mode.name === message.mode);
this.messages.push(message);
},
ON_CLEAR() {
this.messages = [];
this.oldMessages = [];
this.oldMessagesIndex = -1;
},
ON_SUGGESTION_ADD({ suggestion }: { suggestion: Suggestion }) {
this.removedSuggestions = this.removedSuggestions.filter(a => a !== suggestion.name);
const duplicateSuggestion = this.backingSuggestions.find(
a => a.name == suggestion.name
);
if (duplicateSuggestion) {
if (suggestion.help || suggestion.params) {
duplicateSuggestion.help = suggestion.help || "";
duplicateSuggestion.params = suggestion.params || [];
}
return;
}
if (!suggestion.params) {
suggestion.params = []; //TODO Move somewhere else
}
this.backingSuggestions.push(suggestion);
},
ON_SUGGESTION_REMOVE({ name }: { name: string }) {
if (this.removedSuggestions.indexOf(name) <= -1) {
this.removedSuggestions.push(name);
}
},
ON_MODE_ADD({ mode }: { mode: Mode }) {
this.modes = [
...this.modes.filter(a => a.name !== mode.name),
mode
];
},
ON_MODE_REMOVE({ name }: { name: string }) {
this.modes = this.modes.filter(a => a.name !== name);
if (this.modes.length === 0) {
this.modes = [defaultMode];
}
},
ON_TEMPLATE_ADD({ template }: { template: { id: string, html: string }}) {
if (this.templates[template.id]) {
this.warn(`Tried to add duplicate template '${template.id}'`);
} else {
this.templates[template.id] = template.html;
}
},
ON_UPDATE_THEMES({ themes }: { themes: { [key: string]: ThemeData } }) {
this.removeThemes();
this.setThemes(themes);
},
removeThemes() {
for (let i = 0; i < document.styleSheets.length; i++) {
const styleSheet = document.styleSheets[i];
const node = styleSheet.ownerNode as Element;
if (node.getAttribute("data-theme")) {
node.parentNode?.removeChild(node);
}
}
this.tplBackups.reverse();
for (const [elem, oldData] of this.tplBackups) {
elem.innerText = oldData;
}
this.tplBackups = [];
this.msgTplBackups.reverse();
for (const [id, oldData] of this.msgTplBackups) {
this.templates[id] = oldData;
}
this.msgTplBackups = [];
},
setThemes(themes: { [key: string]: ThemeData }) {
for (const [id, data] of Object.entries(themes)) {
if (data.style) {
const style = document.createElement("style");
style.type = "text/css";
style.setAttribute("data-theme", id);
style.appendChild(document.createTextNode(data.style));
document.head.appendChild(style);
}
if (data.styleSheet) {
const link = document.createElement("link");
link.rel = "stylesheet";
link.type = "text/css";
link.href = data.baseUrl + data.styleSheet;
link.setAttribute("data-theme", id);
document.head.appendChild(link);
}
if (data.templates) {
for (const [tplId, tpl] of Object.entries(data.templates)) {
const elem = document.getElementById(tplId);
if (elem) {
this.tplBackups.push([elem, elem.innerText]);
elem.innerText = tpl;
}
}
}
if (data.script) {
const script = document.createElement("script");
script.type = "text/javascript";
script.src = data.baseUrl + data.script;
document.head.appendChild(script);
}
if (data.msgTemplates) {
for (const [tplId, tpl] of Object.entries(data.msgTemplates)) {
this.msgTplBackups.push([tplId, this.templates[tplId]]);
this.templates[tplId] = tpl;
}
}
}
},
warn(msg: string) {
this.messages.push({
args: [msg],
template: "^3<b>CHAT-WARN</b>: ^0{0}"
});
},
clearShowWindowTimer() {
clearTimeout(this.showWindowTimer);
},
resetShowWindowTimer() {
this.clearShowWindowTimer();
this.showWindowTimer = window.setTimeout(() => {
if (this.hideState !== ChatHideStates.AlwaysShow && !this.showInput) {
this.showWindow = false;
}
}, CONFIG.fadeTimeout);
},
keyUp() {
this.resize();
},
keyDown(e: KeyboardEvent) {
if (e.which === 38 || e.which === 40) {
e.preventDefault();
this.moveOldMessageIndex(e.which === 38);
} else if (e.which == 33) {
var buf = document.getElementsByClassName("chat-messages")[0];
buf.scrollTop = buf.scrollTop - 100;
} else if (e.which == 34) {
var buf = document.getElementsByClassName("chat-messages")[0];
buf.scrollTop = buf.scrollTop + 100;
} else if (e.which === 9) { // tab
if (e.shiftKey || e.altKey) {
do {
--this.modeIdx;
if (this.modeIdx < 0) {
this.modeIdx = this.modes.length - 1;
}
} while (this.modes[this.modeIdx].hidden);
} else {
do {
this.modeIdx = (this.modeIdx + 1) % this.modes.length;
} while (this.modes[this.modeIdx].hidden);
}
const buf = document.getElementsByClassName('chat-messages')[0];
setTimeout(() => buf.scrollTop = buf.scrollHeight, 0);
}
this.resize();
},
moveOldMessageIndex(up: boolean) {
if (up && this.oldMessages.length > this.oldMessagesIndex + 1) {
this.oldMessagesIndex += 1;
this.message = this.oldMessages[this.oldMessagesIndex];
} else if (!up && this.oldMessagesIndex - 1 >= 0) {
this.oldMessagesIndex -= 1;
this.message = this.oldMessages[this.oldMessagesIndex];
} else if (!up && this.oldMessagesIndex - 1 === -1) {
this.oldMessagesIndex = -1;
this.message = "";
}
},
resize() {
const input = this.$refs.input as HTMLInputElement;
// scrollHeight includes padding, but content-box excludes padding
// remove padding before setting height on the element
const style = getComputedStyle(input);
const paddingRemove = parseFloat(style.paddingBottom) + parseFloat(style.paddingTop);
input.style.height = "5px";
input.style.height = `${input.scrollHeight - paddingRemove}px`;
},
send() {
if (this.message !== "") {
post(
"http://chat/chatResult",
JSON.stringify({
message: this.message,
mode: this.modes[this.modeIdxGet].name
})
);
this.oldMessages.unshift(this.message);
this.oldMessagesIndex = -1;
this.hideInput();
} else {
this.hideInput(true);
}
},
hideInput(canceled = false) {
setTimeout(() => {
const input = this.$refs.input as HTMLInputElement;
delete input.style.height;
}, 50);
if (canceled) {
post("http://chat/chatResult", JSON.stringify({ canceled }));
}
this.message = "";
this.showInput = false;
clearInterval(this.focusTimer);
if (this.hideState !== ChatHideStates.AlwaysHide) {
this.resetShowWindowTimer();
} else {
this.showWindow = false;
}
}
}
});
-44
View File
@@ -1,44 +0,0 @@
<template>
<div id="app">
<div class="chat-window" :style="this.style" :class="{
'animated': !showWindow && hideAnimated,
'hidden': !showWindow
}">
<div class="chat-messages" ref="messages">
<message v-for="msg in filteredMessages"
:templates="templates"
:multiline="msg.multiline"
:args="msg.args"
:params="msg.params"
:color="msg.color"
:template="msg.template"
:template-id="msg.templateId"
:key="msg.id">
</message>
</div>
</div>
<div class="chat-input">
<div v-show="showInput" class="input">
<span class="prefix" :class="{ any: modes.length > 1 }" :style="{ color: modeColor }">{{modePrefix}}</span>
<textarea v-model="message"
ref="input"
type="text"
autofocus
spellcheck="false"
rows="1"
@keyup.esc="hideInput"
@keyup="keyUp"
@keydown="keyDown"
@keypress.enter.prevent="send">
</textarea>
</div>
<suggestions :message="message" :suggestions="suggestions">
</suggestions>
<div class="chat-hide-state" v-show="showHideState">
{{hideStateString}}
</div>
</div>
</div>
</template>
<script lang="ts" src="./App.ts"></script>
-102
View File
@@ -1,102 +0,0 @@
import CONFIG from './config';
import Vue, { PropType } from 'vue';
export default Vue.component('message', {
data() {
return {};
},
computed: {
textEscaped(): string {
let s = this.template ? this.template : this.templates[this.templateId];
//This hack is required to preserve backwards compatability
if (!this.template && this.templateId == CONFIG.defaultTemplateId
&& this.args.length == 1) {
s = this.templates[CONFIG.defaultAltTemplateId] //Swap out default template :/
}
s = s.replace(`@default`, this.templates[this.templateId]);
s = s.replace(/{(\d+)}/g, (match, number) => {
const argEscaped = this.args[number] != undefined ? this.escape(this.args[number]) : match;
if (number == 0 && this.color) {
//color is deprecated, use templates or ^1 etc.
return this.colorizeOld(argEscaped);
}
return argEscaped;
});
// format variant args
s = s.replace(/\{\{([a-zA-Z0-9_\-]+?)\}\}/g, (match, id) => {
const argEscaped = this.params[id] != undefined ? this.escape(this.params[id]) : match;
return argEscaped;
});
return this.colorize(s);
},
},
methods: {
colorizeOld(str: string): string {
return `<span style="color: rgb(${this.color[0]}, ${this.color[1]}, ${this.color[2]})">${str}</span>`
},
colorize(str: string): string {
let s = "<span>" + colorTrans(str) + "</span>";
const styleDict: {[ key: string ]: string} = {
'*': 'font-weight: bold;',
'_': 'text-decoration: underline;',
'~': 'text-decoration: line-through;',
'=': 'text-decoration: underline line-through;',
'r': 'text-decoration: none;font-weight: normal;',
};
const styleRegex = /\^(\_|\*|\=|\~|\/|r)(.*?)(?=$|\^r|<\/em>)/;
while (s.match(styleRegex)) { //Any better solution would be appreciated :P
s = s.replace(styleRegex, (str, style, inner) => `<em style="${styleDict[style]}">${inner}</em>`)
}
return s.replace(/<span[^>]*><\/span[^>]*>/g, '');
function colorTrans(str: string) {
return str
.replace(/\^([0-9])/g, (str, color) => `</span><span class="color-${color}">`)
.replace(/\^#([0-9A-F]{3,6})/gi, (str, color) => `</span><span class="color" style="color: #${color}">`)
.replace(/~([a-z])~/g, (str, color) => `</span><span class="gameColor-${color}">`);
}
},
escape(unsafe: string): string {
return String(unsafe)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
},
},
props: {
templates: {
type: Object as PropType<{ [key: string]: string }>,
},
args: {
type: Array as PropType<string[]>,
},
params: {
type: Object as PropType<{ [ key: string]: string }>,
},
template: {
type: String,
default: null,
},
templateId: {
type: String,
default: CONFIG.defaultTemplateId,
},
multiline: {
type: Boolean,
default: false,
},
color: { //deprecated
type: Array as PropType<number[]>,
default: null,
},
},
});
@@ -1,7 +0,0 @@
<template>
<div class="msg" :class="{ multiline }">
<span v-html="textEscaped"></span>
</div>
</template>
<script lang="ts" src="./Message.ts"></script>
@@ -1,63 +0,0 @@
import CONFIG from './config';
import Vue, { PropType } from 'vue';
export interface Suggestion {
name: string;
help: string;
params: string[];
disabled: boolean;
}
export default Vue.component('suggestions', {
props: {
message: {
type: String
},
suggestions: {
type: Array as PropType<Suggestion[]>
}
},
data() {
return {};
},
computed: {
currentSuggestions(): Suggestion[] {
if (this.message === '') {
return [];
}
const currentSuggestions = this.suggestions.filter((s) => {
if (!s.name.startsWith(this.message)) {
const suggestionSplitted = s.name.split(' ');
const messageSplitted = this.message.split(' ');
for (let i = 0; i < messageSplitted.length; i += 1) {
if (i >= suggestionSplitted.length) {
return i < suggestionSplitted.length + s.params.length;
}
if (suggestionSplitted[i] !== messageSplitted[i]) {
return false;
}
}
}
return true;
}).slice(0, CONFIG.suggestionLimit);
currentSuggestions.forEach((s) => {
// eslint-disable-next-line no-param-reassign
s.disabled = !s.name.startsWith(this.message);
s.params.forEach((p, index) => {
const wType = (index === s.params.length - 1) ? '.' : '\\S';
const regex = new RegExp(`${s.name} (?:\\w+ ){${index}}(?:${wType}*)$`, 'g');
// eslint-disable-next-line no-param-reassign
// @ts-ignore
p.disabled = this.message.match(regex) == null;
});
});
return currentSuggestions;
},
},
methods: {},
});
@@ -1,29 +0,0 @@
<template>
<div class="suggestions-wrap" v-show="currentSuggestions.length > 0">
<ul class="suggestions">
<li class="suggestion" v-for="s in currentSuggestions" :key="s.name">
<p>
<span :class="{ 'disabled': s.disabled }">
{{s.name}}
</span>
<span class="param"
v-for="p in s.params"
:class="{ 'disabled': p.disabled }"
:key="p.name">
[{{p.name}}]
</span>
</p>
<small class="help">
<template v-if="!s.disabled">
{{s.help}}
</template>
<template v-for="p in s.params" v-if="!p.disabled">
{{p.help}}
</template>
</small>
</li>
</ul>
</div>
</template>
<script lang="ts" src="./Suggestions.ts"></script>
-17
View File
@@ -1,17 +0,0 @@
export default {
defaultTemplateId: 'default', //This is the default template for 2 args1
defaultAltTemplateId: 'defaultAlt', //This one for 1 arg
templates: { //You can add static templates here
'default': '<b>{0}</b>: {1}',
'defaultAlt': '{0}',
'print': '<pre>{0}</pre>',
'example:important': '<h1>^2{0}</h1>'
},
fadeTimeout: 7000,
suggestionLimit: 5,
style: {
background: 'rgba(52, 73, 94, 0.7)',
width: '38vw',
height: '22%',
}
};
-160
View File
@@ -1,160 +0,0 @@
.color-0{color: #ffffff;}
.color-1{color: #ff4444;}
.color-2{color: #99cc00;}
.color-3{color: #ffbb33;}
.color-4{color: #0099cc;}
.color-5{color: #33b5e5;}
.color-6{color: #aa66cc;}
.color-8{color: #cc0000;}
.color-9{color: #cc0068;}
.gameColor-w{color: #ffffff;}
.gameColor-r{color: #ff4444;}
.gameColor-g{color: #99cc00;}
.gameColor-y{color: #ffbb33;}
.gameColor-b{color: #33b5e5;}
/* todo: more game colors */
* {
font-family: 'Lato', sans-serif;
margin: 0;
padding: 0;
}
.no-grow {
flex-grow: 0;
}
em {
font-style: normal;
}
#app {
font-family: 'Lato', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: white;
}
.chat-window {
position: absolute;
top: 1.5%;
left: 0.8%;
width: 38%;
height: 22%;
max-width: 1000px;
background-color: rgba(52, 73, 94, 0.7);
-webkit-animation-duration: 2s;
}
.chat-messages {
position: relative;
height: 95%;
font-size: 1.8vh;
margin: 1%;
overflow-x: hidden;
overflow-y: hidden;
}
.chat-input {
font-size: 1.65vh;
position: absolute;
top: 23.8%;
left: 0.8%;
width: 38%;
max-width: 1000px;
box-sizing: border-box;
}
.chat-input > div.input {
position: relative;
display: flex;
align-items: stretch;
width: 100%;
background-color: rgba(44, 62, 80, 1.0);
}
.chat-hide-state {
text-transform: uppercase;
margin-left: 0.05vw;
font-size: 1.65vh;
}
.prefix {
font-size: 1.8vh;
/*position: absolute;
top: 0%;*/
height: 100%;
vertical-align: middle;
line-height: calc(1vh + 1vh + 1.85vh);
padding-left: 0.5vh;
text-transform: uppercase;
font-weight: bold;
display: inline-block;
}
textarea {
font-size: 1.65vh;
line-height: 1.85vh;
display: block;
box-sizing: content-box;
padding: 1vh;
padding-left: 0.5vh;
color: white;
border-width: 0;
height: 3.15%;
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
background-color: transparent;
}
textarea:focus, input:focus {
outline: none;
}
.msg {
margin-bottom: 0.28%;
}
.multiline {
margin-left: 4%;
text-indent: -1.2rem;
white-space: pre-line;
}
.suggestions {
list-style-type: none;
padding: 0.5%;
padding-left: 1.4%;
font-size: 1.65vh;
box-sizing: border-box;
color: white;
background-color: rgba(44, 62, 80, 1.0);
width: 100%;
}
.help {
color: #b0bbbd;
}
.disabled {
color: #b0bbbd;
}
.suggestion {
margin-bottom: 0.5%;
}
.hidden {
opacity: 0;
}
.hidden.animated {
transition: opacity 1s;
}
-4
View File
@@ -1,4 +0,0 @@
declare module '*.vue' {
import Vue from 'vue'
export default Vue
}
-14
View File
@@ -1,14 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<link href="/html/vendor/latofonts.css" rel="stylesheet">
<link href="/html/vendor/flexboxgrid.6.3.1.min.css" rel="stylesheet"></link>
<link href="/html/vendor/animate.3.5.2.min.css" rel="stylesheet"></link>
<link href="index.css" rel="stylesheet"></link>
</head>
<body>
<div id="app"></div>
</body>
</html>
-7
View File
@@ -1,7 +0,0 @@
import Vue from 'vue';
import App from './App.vue';
const instance = new Vue({
el: '#app',
render: h => h(App),
});
@@ -1,18 +0,0 @@
{
"compilerOptions": {
"outDir": "./",
"module": "es6",
"strict": true,
"moduleResolution": "node",
"target": "es6",
"allowJs": true,
"lib": [
"es2017",
"dom"
]
},
"include": [
"./**/*"
],
"exclude": []
}
-31
View File
@@ -1,31 +0,0 @@
export function post(url: string, data: any) {
var request = new XMLHttpRequest();
request.open('POST', url, true);
request.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
request.send(data);
}
function emulate(type: string, detail = {}) {
const detailRef = {
type,
...detail
};
window.dispatchEvent(new CustomEvent('message', {
detail: detailRef
}));
}
(window as any)['emulate'] = emulate;
(window as any)['demo'] = () => {
emulate('ON_MESSAGE', {
message: {
args: [ 'me', 'hello!' ]
}
})
emulate('ON_SCREEN_STATE_CHANGE', {
shouldHide: false
});
};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-48
View File
@@ -1,48 +0,0 @@
/* latin-ext */
@font-face {
font-family: 'Lato';
font-style: normal;
font-weight: 300;
src: local('Lato Light'), local('Lato-Light'), url(fonts/LatoLight.woff2);
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Lato';
font-style: normal;
font-weight: 300;
src: local('Lato Light'), local('Lato-Light'), url(fonts/LatoLight2.woff2);
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}
/* latin-ext */
@font-face {
font-family: 'Lato';
font-style: normal;
font-weight: 400;
src: local('Lato Regular'), local('Lato-Regular'), url(fonts/LatoRegular.woff2);
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Lato';
font-style: normal;
font-weight: 400;
src: local('Lato Regular'), local('Lato-Regular'), url(fonts/LatoRegular2.woff2);
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}
/* latin-ext */
@font-face {
font-family: 'Lato';
font-style: normal;
font-weight: 700;
src: local('Lato Bold'), local('Lato-Bold'), url(fonts/LatoBold.woff2);
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Lato';
font-style: normal;
font-weight: 700;
src: local('Lato Bold'), local('Lato-Bold'), url(fonts/LatoBold2.woff2);
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}
-24
View File
@@ -1,24 +0,0 @@
{
"name": "chat",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"@types/vue": "^2.0.0",
"copy-webpack-plugin": "^5.1.1",
"css-loader": "^3.4.2",
"html-webpack-inline-source-plugin": "^0.0.10",
"html-webpack-plugin": "^3.2.0",
"ts-loader": "^6.2.1",
"typescript": "^3.8.3",
"vue": "^2.6.11",
"vue-loader": "^15.9.0",
"vue-template-compiler": "^2.6.11",
"webpack": "4"
},
"devDependencies": {
"command-line-args": "^5.1.1",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3"
}
}
-293
View File
@@ -1,293 +0,0 @@
RegisterServerEvent('chat:init')
RegisterServerEvent('chat:addTemplate')
RegisterServerEvent('chat:addMessage')
RegisterServerEvent('chat:addSuggestion')
RegisterServerEvent('chat:removeSuggestion')
RegisterServerEvent('_chat:messageEntered')
RegisterServerEvent('chat:clear')
RegisterServerEvent('__cfx_internal:commandFallback')
-- this is a built-in event, but somehow needs to be registered
RegisterNetEvent('playerJoining')
exports('addMessage', function(target, message)
if not message then
message = target
target = -1
end
if not target or not message then return end
TriggerClientEvent('chat:addMessage', target, message)
end)
local hooks = {}
local hookIdx = 1
exports('registerMessageHook', function(hook)
local resource = GetInvokingResource()
hooks[hookIdx + 1] = {
fn = hook,
resource = resource
}
hookIdx = hookIdx + 1
end)
local modes = {}
local function getMatchingPlayers(seObject)
local players = GetPlayers()
local retval = {}
for _, v in ipairs(players) do
if IsPlayerAceAllowed(v, seObject) then
retval[#retval + 1] = v
end
end
return retval
end
exports('registerMode', function(modeData)
if not modeData.name or not modeData.displayName or not modeData.cb then
return false
end
local resource = GetInvokingResource()
modes[modeData.name] = modeData
modes[modeData.name].resource = resource
local clObj = {
name = modeData.name,
displayName = modeData.displayName,
color = modeData.color or '#fff',
isChannel = modeData.isChannel,
isGlobal = modeData.isGlobal,
}
if not modeData.seObject then
TriggerClientEvent('chat:addMode', -1, clObj)
else
for _, v in ipairs(getMatchingPlayers(modeData.seObject)) do
TriggerClientEvent('chat:addMode', v, clObj)
end
end
return true
end)
local function unregisterHooks(resource)
local toRemove = {}
for k, v in pairs(hooks) do
if v.resource == resource then
table.insert(toRemove, k)
end
end
for _, v in ipairs(toRemove) do
hooks[v] = nil
end
toRemove = {}
for k, v in pairs(modes) do
if v.resource == resource then
table.insert(toRemove, k)
end
end
for _, v in ipairs(toRemove) do
TriggerClientEvent('chat:removeMode', -1, {
name = v
})
modes[v] = nil
end
end
local function routeMessage(source, author, message, mode, fromConsole)
if source >= 1 then
author = GetPlayerName(source)
end
local outMessage = {
color = { 255, 255, 255 },
multiline = true,
args = { message },
mode = mode
}
if author ~= "" then
outMessage.args = { author, message }
end
if mode and modes[mode] then
local modeData = modes[mode]
if modeData.seObject and not IsPlayerAceAllowed(source, modeData.seObject) then
return
end
end
local messageCanceled = false
local routingTarget = -1
local hookRef = {
updateMessage = function(t)
-- shallow merge
for k, v in pairs(t) do
if k == 'template' then
outMessage['template'] = v:gsub('%{%}', outMessage['template'] or '@default')
elseif k == 'params' then
if not outMessage.params then
outMessage.params = {}
end
for pk, pv in pairs(v) do
outMessage.params[pk] = pv
end
else
outMessage[k] = v
end
end
end,
cancel = function()
messageCanceled = true
end,
setSeObject = function(object)
routingTarget = getMatchingPlayers(object)
end,
setRouting = function(target)
routingTarget = target
end
}
for _, hook in pairs(hooks) do
if hook.fn then
hook.fn(source, outMessage, hookRef)
end
end
if modes[mode] then
local m = modes[mode]
m.cb(source, outMessage, hookRef)
end
if messageCanceled then
return
end
TriggerEvent('chatMessage', source, #outMessage.args > 1 and outMessage.args[1] or '', outMessage.args[#outMessage.args])
if not WasEventCanceled() then
if type(routingTarget) ~= 'table' then
TriggerClientEvent('chat:addMessage', routingTarget, outMessage)
else
for _, id in ipairs(routingTarget) do
TriggerClientEvent('chat:addMessage', id, outMessage)
end
end
end
if not fromConsole then
print(author .. '^7' .. (modes[mode] and (' (' .. modes[mode].displayName .. ')') or '') .. ': ' .. message .. '^7')
end
end
AddEventHandler('_chat:messageEntered', function(author, color, message, mode)
if not message or not author then
return
end
local source = source
routeMessage(source, author, message, mode)
end)
AddEventHandler('__cfx_internal:commandFallback', function(command)
local name = GetPlayerName(source)
-- route the message as if it were a /command
routeMessage(source, name, '/' .. command, nil, true)
CancelEvent()
end)
-- player join messages
AddEventHandler('playerJoining', function()
if GetConvarInt('chat_showJoins', 1) == 0 then
return
end
TriggerClientEvent('chatMessage', -1, '', { 255, 255, 255 }, '^2* ' .. GetPlayerName(source) .. ' joined.')
end)
AddEventHandler('playerDropped', function(reason)
if GetConvarInt('chat_showQuits', 1) == 0 then
return
end
TriggerClientEvent('chatMessage', -1, '', { 255, 255, 255 }, '^2* ' .. GetPlayerName(source) ..' left (' .. reason .. ')')
end)
RegisterCommand('say', function(source, args, rawCommand)
routeMessage(source, (source == 0) and 'console' or GetPlayerName(source), rawCommand:sub(5), nil, true)
end)
-- command suggestions for clients
local function refreshCommands(player)
if GetRegisteredCommands then
local registeredCommands = GetRegisteredCommands()
local suggestions = {}
for _, command in ipairs(registeredCommands) do
if IsPlayerAceAllowed(player, ('command.%s'):format(command.name)) then
table.insert(suggestions, {
name = '/' .. command.name,
help = ''
})
end
end
TriggerClientEvent('chat:addSuggestions', player, suggestions)
end
end
AddEventHandler('chat:init', function()
local source = source
refreshCommands(source)
for _, modeData in pairs(modes) do
local clObj = {
name = modeData.name,
displayName = modeData.displayName,
color = modeData.color or '#fff',
isChannel = modeData.isChannel,
isGlobal = modeData.isGlobal,
}
if not modeData.seObject or IsPlayerAceAllowed(source, modeData.seObject) then
TriggerClientEvent('chat:addMode', source, clObj)
end
end
end)
AddEventHandler('onServerResourceStart', function(resName)
Wait(500)
for _, player in ipairs(GetPlayers()) do
refreshCommands(player)
end
end)
AddEventHandler('onResourceStop', function(resName)
unregisterHooks(resName)
end)
@@ -1,45 +0,0 @@
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const CopyPlugin = require('copy-webpack-plugin');
module.exports = {
mode: 'production',
entry: './html/main.ts',
module: {
rules: [
{
test: /\.ts$/,
loader: 'ts-loader',
exclude: /node_modules/,
options: {
appendTsSuffixTo: [/\.vue$/],
}
},
{
test: /\.vue$/,
loader: 'vue-loader'
},
]
},
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
inlineSource: '.(js|css)$',
template: './html/index.html',
filename: 'ui.html'
}),
new HtmlWebpackInlineSourcePlugin(),
new CopyPlugin([
{ from: 'html/index.css', to: 'index.css' }
]),
],
resolve: {
extensions: [ '.ts', '.js' ]
},
output: {
filename: 'chat.js',
path: __dirname + '/dist/'
},
//devtool: 'inline-source-map'
};
File diff suppressed because it is too large Load Diff
@@ -216,16 +216,14 @@ AddEventHandler('onResourceStop', function(resource)
unloadMap(resource) unloadMap(resource)
end) end)
AddEventHandler('rconCommand', function(commandName, args) RegisterCommand("map", function(source, args)
if commandName == 'map' then
if #args ~= 1 then if #args ~= 1 then
RconPrint("usage: map [mapname]\n") print("usage: map [mapname]")
return
end end
if not maps[args[1]] then if not maps[args[1]] then
RconPrint('no such map ' .. args[1] .. "\n") print('no such map ' .. args[1] .. '\n')
CancelEvent()
return return
end end
@@ -247,39 +245,25 @@ AddEventHandler('rconCommand', function(commandName, args)
changeGameType(gt) changeGameType(gt)
changeMap(args[1]) changeMap(args[1])
RconPrint('map ' .. args[1] .. "\n") print('map ' .. args[1] .. "\n")
else else
RconPrint('map ' .. args[1] .. ' does not support ' .. currentGameType .. "\n") print('map ' .. args[1] .. ' does not support ' .. currentGameType .. "\n")
end end
CancelEvent()
return
end end
end, true)
changeMap(args[1]) RegisterCommand("gametype", function(source, args)
RconPrint('map ' .. args[1] .. "\n")
CancelEvent()
elseif commandName == 'gametype' then
if #args ~= 1 then if #args ~= 1 then
RconPrint("usage: gametype [name]\n") print("usage: gametype [name]\n")
return
end end
if not gametypes[args[1]] then if not gametypes[args[1]] then
RconPrint('no such gametype ' .. args[1] .. "\n") print('no such gametype ' .. args[1] .. "\n")
CancelEvent()
return return
end end
changeGameType(args[1]) changeGameType(args[1])
print('gametype ' .. args[1] .. "\n")
RconPrint('gametype ' .. args[1] .. "\n")
CancelEvent()
end
end) end)
function getCurrentGameType() function getCurrentGameType()
@@ -1,2 +0,0 @@
.yarn.installed
node_modules/
@@ -1,13 +0,0 @@
-- This resource is part of the default Cfx.re asset pack (cfx-server-data)
-- Altering or recreating for local use only is strongly discouraged.
version '1.0.0'
author 'Cfx.re <[email protected]>'
description 'Builds resources with webpack. To learn more: https://webpack.js.org'
repository 'https://github.com/citizenfx/cfx-server-data'
dependency 'yarn'
server_script 'webpack_builder.js'
fx_version 'adamant'
game 'common'
@@ -1,16 +0,0 @@
{
"name": "webpack-builder",
"version": "1.0.1",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"async": "^3.1.0",
"webpack": "^4.41.2",
"worker-farm": "^1.7.0"
}
}
@@ -1,173 +0,0 @@
const fs = require('fs');
const path = require('path');
const workerFarm = require('worker-farm');
const async = require('async');
let buildingInProgress = false;
let currentBuildingModule = '';
// some modules will not like the custom stack trace logic
const ops = Error.prepareStackTrace;
Error.prepareStackTrace = undefined;
const webpackBuildTask = {
shouldBuild(resourceName) {
const numMetaData = GetNumResourceMetadata(resourceName, 'webpack_config');
if (numMetaData > 0) {
for (let i = 0; i < numMetaData; i++) {
const configName = GetResourceMetadata(resourceName, 'webpack_config');
if (shouldBuild(configName)) {
return true;
}
}
}
return false;
function loadCache(config) {
const cachePath = `cache/${resourceName}/${config.replace(/\//g, '_')}.json`;
try {
return JSON.parse(fs.readFileSync(cachePath, {encoding: 'utf8'}));
} catch {
return null;
}
}
function shouldBuild(config) {
const cache = loadCache(config);
if (!cache) {
return true;
}
for (const file of cache) {
const stats = getStat(file.name);
if (!stats ||
stats.mtime !== file.stats.mtime ||
stats.size !== file.stats.size ||
stats.inode !== file.stats.inode) {
return true;
}
}
return false;
}
function getStat(path) {
try {
const stat = fs.statSync(path);
return stat ? {
mtime: stat.mtimeMs,
size: stat.size,
inode: stat.ino,
} : null;
} catch {
return null;
}
}
},
build(resourceName, cb) {
let buildWebpack = async () => {
let error = null;
const configs = [];
const promises = [];
const numMetaData = GetNumResourceMetadata(resourceName, 'webpack_config');
for (let i = 0; i < numMetaData; i++) {
configs.push(GetResourceMetadata(resourceName, 'webpack_config', i));
}
for (const configName of configs) {
const configPath = GetResourcePath(resourceName) + '/' + configName;
const cachePath = `cache/${resourceName}/${configName.replace(/\//g, '_')}.json`;
try {
fs.mkdirSync(path.dirname(cachePath));
} catch {
}
const config = require(configPath);
const workers = workerFarm(require.resolve('./webpack_runner'));
if (config) {
const resourcePath = path.resolve(GetResourcePath(resourceName));
while (buildingInProgress) {
console.log(`webpack is busy: we are waiting to compile ${resourceName} (${configName})`);
await sleep(3000);
}
console.log(`${resourceName}: started building ${configName}`);
buildingInProgress = true;
currentBuildingModule = resourceName;
promises.push(new Promise((resolve, reject) => {
workers({
configPath,
resourcePath,
cachePath
}, (err, outp) => {
workerFarm.end(workers);
if (err) {
console.error(err.stack || err);
if (err.details) {
console.error(err.details);
}
buildingInProgress = false;
currentBuildingModule = '';
currentBuildingScript = '';
reject("worker farm webpack errored out");
return;
}
if (outp.errors) {
for (const error of outp.errors) {
console.log(error);
}
buildingInProgress = false;
currentBuildingModule = '';
currentBuildingScript = '';
reject("webpack got an error");
return;
}
console.log(`${resourceName}: built ${configName}`);
buildingInProgress = false;
resolve();
});
}));
}
}
try {
await Promise.all(promises);
} catch (e) {
error = e.toString();
}
buildingInProgress = false;
currentBuildingModule = '';
if (error) {
cb(false, error);
} else cb(true);
};
buildWebpack().then();
}
};
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
RegisterResourceBuildTaskFactory('z_webpack', () => webpackBuildTask);
@@ -1,75 +0,0 @@
const webpack = require('webpack');
const path = require('path');
const fs = require('fs');
function getStat(path) {
try {
const stat = fs.statSync(path);
return stat ? {
mtime: stat.mtimeMs,
size: stat.size,
inode: stat.ino,
} : null;
} catch {
return null;
}
}
class SaveStatePlugin {
constructor(inp) {
this.cache = [];
this.cachePath = inp.cachePath;
}
apply(compiler) {
compiler.hooks.afterCompile.tap('SaveStatePlugin', (compilation) => {
for (const file of compilation.fileDependencies) {
this.cache.push({
name: file,
stats: getStat(file)
});
}
});
compiler.hooks.done.tap('SaveStatePlugin', (stats) => {
if (stats.hasErrors()) {
return;
}
fs.writeFile(this.cachePath, JSON.stringify(this.cache), () => {
});
});
}
}
module.exports = (inp, callback) => {
const config = require(inp.configPath);
config.context = inp.resourcePath;
if (config.output && config.output.path) {
config.output.path = path.resolve(inp.resourcePath, config.output.path);
}
if (!config.plugins) {
config.plugins = [];
}
config.plugins.push(new SaveStatePlugin(inp));
webpack(config, (err, stats) => {
if (err) {
callback(err);
return;
}
if (stats.hasErrors()) {
callback(null, stats.toJson());
return;
}
callback(null, {});
});
};
File diff suppressed because it is too large Load Diff
@@ -1,12 +0,0 @@
-- This resource is part of the default Cfx.re asset pack (cfx-server-data)
-- Altering or recreating for local use only is strongly discouraged.
version '1.0.0'
author 'Cfx.re <[email protected]>'
description 'Builds resources with yarn. To learn more: https://classic.yarnpkg.com'
repository 'https://github.com/citizenfx/cfx-server-data'
fx_version 'adamant'
game 'common'
server_script 'yarn_builder.js'
@@ -1,81 +0,0 @@
const path = require('path');
const fs = require('fs');
const child_process = require('child_process');
let buildingInProgress = false;
let currentBuildingModule = '';
const initCwd = process.cwd();
const trimOutput = (data) => {
return `[yarn]\t` + data.toString().replace(/\s+$/, '');
}
const yarnBuildTask = {
shouldBuild(resourceName) {
try {
const resourcePath = GetResourcePath(resourceName);
const packageJson = path.resolve(resourcePath, 'package.json');
const yarnLock = path.resolve(resourcePath, '.yarn.installed');
const packageStat = fs.statSync(packageJson);
try {
const yarnStat = fs.statSync(yarnLock);
if (packageStat.mtimeMs > yarnStat.mtimeMs) {
return true;
}
} catch (e) {
// no yarn.installed, but package.json - install time!
return true;
}
} catch (e) {
}
return false;
},
build(resourceName, cb) {
(async () => {
while (buildingInProgress && currentBuildingModule !== resourceName) {
console.log(`yarn is currently busy: we are waiting to compile ${resourceName}`);
await sleep(3000);
}
buildingInProgress = true;
currentBuildingModule = resourceName;
const proc = child_process.fork(
require.resolve('./yarn_cli.js'),
['install', '--ignore-scripts', '--cache-folder', path.join(initCwd, 'cache', 'yarn-cache'), '--mutex', 'file:' + path.join(initCwd, 'cache', 'yarn-mutex')],
{
cwd: path.resolve(GetResourcePath(resourceName)),
stdio: 'pipe',
});
proc.stdout.on('data', (data) => console.log(trimOutput(data)));
proc.stderr.on('data', (data) => console.error(trimOutput(data)));
proc.on('exit', (code, signal) => {
setImmediate(() => {
if (code != 0 || signal) {
buildingInProgress = false;
currentBuildingModule = '';
cb(false, 'yarn failed!');
return;
}
const resourcePath = GetResourcePath(resourceName);
const yarnLock = path.resolve(resourcePath, '.yarn.installed');
fs.writeFileSync(yarnLock, '');
buildingInProgress = false;
currentBuildingModule = '';
cb(true);
});
});
})();
}
};
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
RegisterResourceBuildTaskFactory('yarn', () => yarnBuildTask);
File diff suppressed because one or more lines are too long
@@ -33,8 +33,8 @@ Citizen.CreateThread(function()
end end
end end
local killerid = GetPlayerByEntityID(killer) local killerid = NetworkGetPlayerIndexFromPed(killer)
if killer ~= ped and killerid ~= nil and NetworkIsPlayerActive(killerid) then killerid = GetPlayerServerId(killerid) if killer ~= ped and killerid ~= -1 and NetworkIsPlayerActive(killerid) then killerid = GetPlayerServerId(killerid)
else killerid = -1 else killerid = -1
end end
@@ -64,10 +64,3 @@ Citizen.CreateThread(function()
end end
end end
end) end)
function GetPlayerByEntityID(id)
for i=0,32 do
if(NetworkIsPlayerActive(i) and GetPlayerPed(i) == id) then return i end
end
return nil
end
-11
View File
@@ -1,11 +0,0 @@
Citizen.CreateThread(function()
while true do
Wait(0)
if NetworkIsSessionStarted() then
TriggerServerEvent('hardcap:playerActivated')
return
end
end
end)
-14
View File
@@ -1,14 +0,0 @@
-- This resource is part of the default Cfx.re asset pack (cfx-server-data)
-- Altering or recreating for local use only is strongly discouraged.
version '1.0.0'
author 'Cfx.re <[email protected]>'
description 'Limits the number of players to the amount set by sv_maxclients in your server.cfg.'
repository 'https://github.com/citizenfx/cfx-server-data'
client_script 'client.lua'
server_script 'server.lua'
fx_version 'adamant'
games { 'gta5', 'rdr3' }
rdr3_warning 'I acknowledge that this is a prerelease build of RedM, and I am aware my resources *will* become incompatible once RedM ships.'
-31
View File
@@ -1,31 +0,0 @@
local playerCount = 0
local list = {}
RegisterServerEvent('hardcap:playerActivated')
AddEventHandler('hardcap:playerActivated', function()
if not list[source] then
playerCount = playerCount + 1
list[source] = true
end
end)
AddEventHandler('playerDropped', function()
if list[source] then
playerCount = playerCount - 1
list[source] = nil
end
end)
AddEventHandler('playerConnecting', function(name, setReason)
local cv = GetConvarInt('sv_maxclients', 32)
print('Connecting: ' .. name .. '^7')
if playerCount >= cv then
print('Full. :(')
setReason('This server is full (past ' .. tostring(cv) .. ' players).')
CancelEvent()
end
end)
-15
View File
@@ -1,15 +0,0 @@
-- This resource is part of the default Cfx.re asset pack (cfx-server-data)
-- Altering or recreating for local use only is strongly discouraged.
version '1.0.0'
author 'Cfx.re <[email protected]>'
description 'Handles old-style server player management commands.'
repository 'https://github.com/citizenfx/cfx-server-data'
client_script 'rconlog_client.lua'
server_script 'rconlog_server.lua'
fx_version 'adamant'
games { 'gta5', 'rdr3' }
rdr3_warning 'I acknowledge that this is a prerelease build of RedM, and I am aware my resources *will* become incompatible once RedM ships.'
@@ -1,25 +0,0 @@
RegisterNetEvent('rlUpdateNames')
AddEventHandler('rlUpdateNames', function()
local names = {}
for i = 0, 31 do
if NetworkIsPlayerActive(i) then
names[GetPlayerServerId(i)] = { id = i, name = GetPlayerName(i) }
end
end
TriggerServerEvent('rlUpdateNamesResult', names)
end)
Citizen.CreateThread(function()
while true do
Wait(0)
if NetworkIsSessionStarted() then
TriggerServerEvent('rlPlayerActivated')
return
end
end
end)
@@ -1,84 +0,0 @@
RconLog({ msgType = 'serverStart', hostname = 'lovely', maxplayers = 32 })
RegisterServerEvent('rlPlayerActivated')
local names = {}
AddEventHandler('rlPlayerActivated', function()
RconLog({ msgType = 'playerActivated', netID = source, name = GetPlayerName(source), guid = GetPlayerIdentifiers(source)[1], ip = GetPlayerEP(source) })
names[source] = { name = GetPlayerName(source), id = source }
if GetHostId() then
TriggerClientEvent('rlUpdateNames', GetHostId())
end
end)
RegisterServerEvent('rlUpdateNamesResult')
AddEventHandler('rlUpdateNamesResult', function(res)
if source ~= tonumber(GetHostId()) then
print('bad guy')
return
end
for id, data in pairs(res) do
if data then
if data.name then
if not names[id] then
names[id] = data
end
if names[id].name ~= data.name or names[id].id ~= data.id then
names[id] = data
RconLog({ msgType = 'playerRenamed', netID = id, name = data.name })
end
end
else
names[id] = nil
end
end
end)
AddEventHandler('playerDropped', function()
RconLog({ msgType = 'playerDropped', netID = source, name = GetPlayerName(source) })
names[source] = nil
end)
AddEventHandler('chatMessage', function(netID, name, message)
RconLog({ msgType = 'chatMessage', netID = netID, name = name, message = message, guid = GetPlayerIdentifiers(netID)[1] })
end)
-- NOTE: DO NOT USE THIS METHOD FOR HANDLING COMMANDS
-- This resource has not been updated to use newer methods such as RegisterCommand.
AddEventHandler('rconCommand', function(commandName, args)
if commandName == 'status' then
for netid, data in pairs(names) do
local guid = GetPlayerIdentifiers(netid)
if guid and guid[1] and data then
local ping = GetPlayerPing(netid)
RconPrint(netid .. ' ' .. guid[1] .. ' ' .. data.name .. ' ' .. GetPlayerEP(netid) .. ' ' .. ping .. "\n")
end
end
CancelEvent()
elseif commandName:lower() == 'clientkick' then
local playerId = table.remove(args, 1)
local msg = table.concat(args, ' ')
DropPlayer(playerId, msg)
CancelEvent()
elseif commandName:lower() == 'tempbanclient' then
local playerId = table.remove(args, 1)
local msg = table.concat(args, ' ')
TempBanPlayer(playerId, msg)
CancelEvent()
end
end)
@@ -19,6 +19,7 @@ shared_script 'runcode.js'
client_script 'runcode_ui.lua' client_script 'runcode_ui.lua'
ui_page 'web/nui.html' ui_page 'web/nui.html'
nui_callback_strict_mode 'false'
files { files {
'web/nui.html' 'web/nui.html'
} }
@@ -1,2 +0,0 @@
node_modules/
.yarn.installed
@@ -1,17 +0,0 @@
-- This resource is part of the default Cfx.re asset pack (cfx-server-data)
-- Altering or recreating for local use only is strongly discouraged.
version '1.0.0'
author 'Cfx.re <[email protected]>'
description 'Handles Social Club conductor session API for RedM. Do not disable.'
repository 'https://github.com/citizenfx/cfx-server-data'
fx_version 'adamant'
game 'rdr3'
rdr3_warning 'I acknowledge that this is a prerelease build of RedM, and I am aware my resources *will* become incompatible once RedM ships.'
dependencies {
'yarn'
}
server_script 'sm_server.js'
@@ -1,6 +0,0 @@
{
"private": true,
"dependencies": {
"@citizenfx/protobufjs": "6.8.8"
}
}
@@ -1,192 +0,0 @@
syntax = "proto3";
package rline;
message RpcErrorData {
string ErrorCodeString = 1;
int32 ErrorCode = 2;
string DomainString = 3;
int32 DomainCode = 4;
bytes DataEx = 5;
};
message RpcError {
int32 ErrorCode = 1;
string ErrorMessage = 2;
RpcErrorData Data = 3;
};
message RpcHeader {
string RequestId = 1;
string MethodName = 2;
RpcError Error = 3;
string srcTid = 4;
};
message RpcMessage {
RpcHeader Header = 1;
bytes Content = 2;
};
message RpcResponseContainer {
bytes Content = 1;
};
message RpcResponseMessage {
RpcHeader Header = 1;
RpcResponseContainer Container = 2;
};
message TokenStuff {
string tkn = 1;
};
message InitSessionResponse {
bytes sesid = 1;
TokenStuff token = 2;
};
message MpGamerHandleDto {
string gh = 1;
};
message MpPeerAddressDto {
string addr = 1;
};
message InitPlayer2_Parameters {
MpGamerHandleDto gh = 1;
MpPeerAddressDto peerAddress = 2;
int32 discriminator = 3;
int32 seamlessType = 4;
uint32 connectionReason = 5;
};
message InitPlayerResult {
uint32 code = 1;
};
message Restriction {
int32 u1 = 1;
int32 u2 = 2;
int32 u3 = 3;
}
message GetRestrictionsData {
repeated Restriction restriction = 1;
repeated string unk2 = 2;
};
message GetRestrictionsResult {
GetRestrictionsData data = 1;
};
message PlayerIdSto {
int32 acctId = 1;
int32 platId = 2;
};
message MpSessionRequestIdDto {
PlayerIdSto requestor = 1;
int32 index = 2;
int32 hash = 3;
};
message QueueForSession_Seamless_Parameters {
MpSessionRequestIdDto requestId = 1;
uint32 optionFlags = 2;
int32 x = 3;
int32 y = 4;
};
message QueueForSessionResult {
uint32 code = 1;
};
message QueueEntered_Parameters {
uint32 queueGroup = 1;
MpSessionRequestIdDto requestId = 2;
uint32 optionFlags = 3;
};
message GuidDto {
fixed64 a = 1;
fixed64 b = 2;
};
message MpTransitionIdDto {
GuidDto value = 1;
};
message MpSessionIdDto {
GuidDto value = 1;
};
message SessionSubcommandEnterSession {
int32 index = 1;
int32 hindex = 2;
uint32 sessionFlags = 3;
uint32 mode = 4;
int32 size = 5;
int32 teamIndex = 6;
MpTransitionIdDto transitionId = 7;
uint32 sessionManagerType = 8;
int32 slotCount = 9;
};
message SessionSubcommandLeaveSession {
uint32 reason = 1;
};
message SessionSubcommandAddPlayer {
PlayerIdSto id = 1;
MpGamerHandleDto gh = 2;
MpPeerAddressDto addr = 3;
int32 index = 4;
};
message SessionSubcommandRemovePlayer {
PlayerIdSto id = 1;
};
message SessionSubcommandHostChanged {
int32 index = 1;
};
message SessionCommand {
uint32 cmd = 1;
string cmdname = 2;
SessionSubcommandEnterSession EnterSession = 3;
SessionSubcommandLeaveSession LeaveSession = 4;
SessionSubcommandAddPlayer AddPlayer = 5;
SessionSubcommandRemovePlayer RemovePlayer = 6;
SessionSubcommandHostChanged HostChanged = 7;
};
message scmds_Parameters {
MpSessionIdDto sid = 1;
int32 ncmds = 2;
repeated SessionCommand cmds = 3;
};
message UriType {
string url = 1;
};
message TransitionReady_PlayerQueue_Parameters {
UriType serverUri = 1;
uint32 serverSandbox = 2;
MpTransitionIdDto id = 3;
uint32 sessionType = 4;
MpSessionRequestIdDto requestId = 5;
MpSessionIdDto transferId = 6;
};
message TransitionToSession_Parameters {
MpTransitionIdDto id = 1;
float x = 2;
float y = 3;
};
message TransitionToSessionResult {
uint32 code = 1;
};
@@ -1,328 +0,0 @@
const protobuf = require("@citizenfx/protobufjs");
const playerDatas = {};
let slotsUsed = 0;
function assignSlotId() {
for (let i = 0; i < 32; i++) {
if (!(slotsUsed & (1 << i))) {
slotsUsed |= (1 << i);
return i;
}
}
return -1;
}
let hostIndex = -1;
const isOneSync = GetConvar("onesync", "off") !== "off";
protobuf.load(GetResourcePath(GetCurrentResourceName()) + "/rline.proto", function(err, root) {
if (err) {
console.log(err);
return;
}
const RpcMessage = root.lookupType("rline.RpcMessage");
const RpcResponseMessage = root.lookupType("rline.RpcResponseMessage");
const InitSessionResponse = root.lookupType("rline.InitSessionResponse");
const InitPlayer2_Parameters = root.lookupType("rline.InitPlayer2_Parameters");
const InitPlayerResult = root.lookupType("rline.InitPlayerResult");
const GetRestrictionsResult = root.lookupType("rline.GetRestrictionsResult");
const QueueForSession_Seamless_Parameters = root.lookupType("rline.QueueForSession_Seamless_Parameters");
const QueueForSessionResult = root.lookupType("rline.QueueForSessionResult");
const QueueEntered_Parameters = root.lookupType("rline.QueueEntered_Parameters");
const TransitionReady_PlayerQueue_Parameters = root.lookupType("rline.TransitionReady_PlayerQueue_Parameters");
const TransitionToSession_Parameters = root.lookupType("rline.TransitionToSession_Parameters");
const TransitionToSessionResult = root.lookupType("rline.TransitionToSessionResult");
const scmds_Parameters = root.lookupType("rline.scmds_Parameters");
function toArrayBuffer(buf) {
var ab = new ArrayBuffer(buf.length);
var view = new Uint8Array(ab);
for (var i = 0; i < buf.length; ++i) {
view[i] = buf[i];
}
return ab;
}
function emitMsg(target, data) {
emitNet('__cfx_internal:pbRlScSession', target, toArrayBuffer(data));
}
function emitSessionCmds(target, cmd, cmdname, msg) {
const stuff = {};
stuff[cmdname] = msg;
emitMsg(target, RpcMessage.encode({
Header: {
MethodName: 'scmds'
},
Content: scmds_Parameters.encode({
sid: {
value: {
a: 2,
b: 2
}
},
ncmds: 1,
cmds: [
{
cmd,
cmdname,
...stuff
}
]
}).finish()
}).finish());
}
function emitAddPlayer(target, msg) {
emitSessionCmds(target, 2, 'AddPlayer', msg);
}
function emitRemovePlayer(target, msg) {
emitSessionCmds(target, 3, 'RemovePlayer', msg);
}
function emitHostChanged(target, msg) {
emitSessionCmds(target, 5, 'HostChanged', msg);
}
onNet('playerDropped', () => {
if (isOneSync) {
return;
}
try {
const oData = playerDatas[source];
delete playerDatas[source];
if (oData && hostIndex === oData.slot) {
const pda = Object.entries(playerDatas);
if (pda.length > 0) {
hostIndex = pda[0][1].slot | 0; // TODO: actually use <=31 slot index *and* check for id
for (const [ id, data ] of Object.entries(playerDatas)) {
emitHostChanged(id, {
index: hostIndex
});
}
} else {
hostIndex = -1;
}
}
if (!oData) {
return;
}
if (oData.slot > -1) {
slotsUsed &= ~(1 << oData.slot);
}
for (const [ id, data ] of Object.entries(playerDatas)) {
emitRemovePlayer(id, {
id: oData.id
});
}
} catch (e) {
console.log(e);
console.log(e.stack);
}
});
function makeResponse(type, data) {
return {
Header: {
},
Container: {
Content: type.encode(data).finish()
}
};
}
const handlers = {
async InitSession(source, data) {
return makeResponse(InitSessionResponse, {
sesid: Buffer.alloc(16),
/*token: {
tkn: 'ACSTOKEN token="meow",signature="meow"'
}*/
});
},
async InitPlayer2(source, data) {
const req = InitPlayer2_Parameters.decode(data);
if (!isOneSync) {
playerDatas[source] = {
gh: req.gh,
peerAddress: req.peerAddress,
discriminator: req.discriminator,
slot: -1
};
}
return makeResponse(InitPlayerResult, {
code: 0
});
},
async GetRestrictions(source, data) {
return makeResponse(GetRestrictionsResult, {
data: {
}
});
},
async ConfirmSessionEntered(source, data) {
return {};
},
async TransitionToSession(source, data) {
const req = TransitionToSession_Parameters.decode(data);
return makeResponse(TransitionToSessionResult, {
code: 1 // in this message, 1 is success
});
},
async QueueForSession_Seamless(source, data) {
const req = QueueForSession_Seamless_Parameters.decode(data);
if (!isOneSync) {
playerDatas[source].req = req.requestId;
playerDatas[source].id = req.requestId.requestor;
playerDatas[source].slot = assignSlotId();
}
setTimeout(() => {
emitMsg(source, RpcMessage.encode({
Header: {
MethodName: 'QueueEntered'
},
Content: QueueEntered_Parameters.encode({
queueGroup: 69,
requestId: req.requestId,
optionFlags: req.optionFlags
}).finish()
}).finish());
if (isOneSync) {
hostIndex = 16
} else if (hostIndex === -1) {
hostIndex = playerDatas[source].slot | 0;
}
emitMsg(source, RpcMessage.encode({
Header: {
MethodName: 'TransitionReady_PlayerQueue'
},
Content: TransitionReady_PlayerQueue_Parameters.encode({
serverUri: {
url: ''
},
requestId: req.requestId,
id: {
value: {
a: 2,
b: 0
}
},
serverSandbox: 0xD656C677,
sessionType: 3,
transferId: {
value: {
a: 2,
b: 2
}
},
}).finish()
}).finish());
setTimeout(() => {
emitSessionCmds(source, 0, 'EnterSession', {
index: (isOneSync) ? 16 : playerDatas[source].slot | 0,
hindex: hostIndex,
sessionFlags: 0,
mode: 0,
size: (isOneSync) ? 0 : Object.entries(playerDatas).filter(a => a[1].id).length,
//size: 2,
//size: Object.entries(playerDatas).length,
teamIndex: 0,
transitionId: {
value: {
a: 2,
b: 0
}
},
sessionManagerType: 0,
slotCount: 32
});
}, 50);
if (!isOneSync) {
setTimeout(() => {
// tell player about everyone, and everyone about player
const meData = playerDatas[source];
const aboutMe = {
id: meData.id,
gh: meData.gh,
addr: meData.peerAddress,
index: playerDatas[source].slot | 0
};
for (const [ id, data ] of Object.entries(playerDatas)) {
if (id == source || !data.id) continue;
emitAddPlayer(source, {
id: data.id,
gh: data.gh,
addr: data.peerAddress,
index: data.slot | 0
});
emitAddPlayer(id, aboutMe);
}
}, 150);
}
}, 250);
return makeResponse(QueueForSessionResult, {
code: 1
});
},
};
async function handleMessage(source, method, data) {
if (handlers[method]) {
return await handlers[method](source, data);
}
return {};
}
onNet('__cfx_internal:pbRlScSession', async (data) => {
const s = source;
try {
const message = RpcMessage.decode(new Uint8Array(data));
const response = await handleMessage(s, message.Header.MethodName, message.Content);
if (!response || !response.Header) {
return;
}
response.Header.RequestId = message.Header.RequestId;
emitMsg(s, RpcResponseMessage.encode(response).finish());
} catch (e) {
console.log(e);
console.log(e.stack);
}
});
});
@@ -1,90 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@citizenfx/[email protected]":
version "6.8.8"
resolved "https://registry.yarnpkg.com/@citizenfx/protobufjs/-/protobufjs-6.8.8.tgz#d12edcada06182f3785dea1d35eebf0ebe4fd2c3"
integrity sha512-RBJvHPWNwguEPxV+ALbCZBXAEQf2byP5KtYrYl36Mbb0+Ch7MxpOm+764S+YqYWj/A/iNSW3jXglfJ9hlxjBLA==
dependencies:
"@protobufjs/aspromise" "^1.1.2"
"@protobufjs/base64" "^1.1.2"
"@protobufjs/codegen" "^2.0.4"
"@protobufjs/eventemitter" "^1.1.0"
"@protobufjs/fetch" "^1.1.0"
"@protobufjs/float" "^1.0.2"
"@protobufjs/inquire" "^1.1.0"
"@protobufjs/path" "^1.1.2"
"@protobufjs/pool" "^1.1.0"
"@protobufjs/utf8" "^1.1.0"
"@types/long" "^4.0.0"
"@types/node" "^10.1.0"
long "^4.0.0"
"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf"
integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78=
"@protobufjs/base64@^1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735"
integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==
"@protobufjs/codegen@^2.0.4":
version "2.0.4"
resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb"
integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==
"@protobufjs/eventemitter@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70"
integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A=
"@protobufjs/fetch@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45"
integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=
dependencies:
"@protobufjs/aspromise" "^1.1.1"
"@protobufjs/inquire" "^1.1.0"
"@protobufjs/float@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1"
integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=
"@protobufjs/inquire@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089"
integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=
"@protobufjs/path@^1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d"
integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=
"@protobufjs/pool@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54"
integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=
"@protobufjs/utf8@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=
"@types/long@^4.0.0":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9"
integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==
"@types/node@^10.1.0":
version "10.17.58"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.58.tgz#10682f6016fd866725c36d22ce6bbbd029bf4545"
integrity sha512-Dn5RBxLohjdHFj17dVVw3rtrZAeXeWg+LQfvxDIW/fdPkSiuQk7h3frKMYtsQhtIW42wkErDcy9UMVxhGW4O7w==
long@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==
@@ -1,3 +0,0 @@
--This empty file causes the scheduler.lua to load clientside
--scheduler.lua when loaded inside the sessionmanager resource currently manages remote callbacks.
--Without this, callbacks will only work server->client and not client->server.
@@ -1,13 +0,0 @@
-- This resource is part of the default Cfx.re asset pack (cfx-server-data)
-- Altering or recreating for local use only is strongly discouraged.
version '1.0.0'
author 'Cfx.re <[email protected]>'
description 'Handles the "host lock" for non-OneSync servers. Do not disable.'
repository 'https://github.com/citizenfx/cfx-server-data'
fx_version 'cerulean'
games { 'gta4', 'gta5' }
server_script 'server/host_lock.lua'
client_script 'client/empty.lua'
@@ -1,69 +0,0 @@
-- whitelist c2s events
RegisterServerEvent('hostingSession')
RegisterServerEvent('hostedSession')
-- event handler for pre-session 'acquire'
local currentHosting
local hostReleaseCallbacks = {}
-- TODO: add a timeout for the hosting lock to be held
-- TODO: add checks for 'fraudulent' conflict cases of hosting attempts (typically whenever the host can not be reached)
AddEventHandler('hostingSession', function()
-- if the lock is currently held, tell the client to await further instruction
if currentHosting then
TriggerClientEvent('sessionHostResult', source, 'wait')
-- register a callback for when the lock is freed
table.insert(hostReleaseCallbacks, function()
TriggerClientEvent('sessionHostResult', source, 'free')
end)
return
end
-- if the current host was last contacted less than a second ago
if GetHostId() then
if GetPlayerLastMsg(GetHostId()) < 1000 then
TriggerClientEvent('sessionHostResult', source, 'conflict')
return
end
end
hostReleaseCallbacks = {}
currentHosting = source
TriggerClientEvent('sessionHostResult', source, 'go')
-- set a timeout of 5 seconds
SetTimeout(5000, function()
if not currentHosting then
return
end
currentHosting = nil
for _, cb in ipairs(hostReleaseCallbacks) do
cb()
end
end)
end)
AddEventHandler('hostedSession', function()
-- check if the client is the original locker
if currentHosting ~= source then
-- TODO: drop client as they're clearly lying
print(currentHosting, '~=', source)
return
end
-- free the host lock (call callbacks and remove the lock value)
for _, cb in ipairs(hostReleaseCallbacks) do
cb()
end
currentHosting = nil
end)
EnableEnhancedHostSupport(true)
-13
View File
@@ -1,13 +0,0 @@
-- This resource is part of the default Cfx.re asset pack (cfx-server-data)
-- Altering or recreating for local use only is strongly discouraged.
version '1.0.0'
author 'Cfx.re <[email protected]>'
description 'A compatibility resource to load basic-gamemode.'
repository 'https://github.com/citizenfx/cfx-server-data'
-- compatibility wrapper
fx_version 'adamant'
game 'common'
dependency 'basic-gamemode'