commit ee4dd896939401b12626883bd743d5489ee7ae0e Author: guava Date: Thu Dec 15 13:40:07 2016 +0100 initial diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..68975d3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +CitizenMP.Server.exe +CitizenMP.Server.exe.config +NLog.config +/cache/ diff --git a/bin/README.txt b/bin/README.txt new file mode 100644 index 0000000..9b54097 --- /dev/null +++ b/bin/README.txt @@ -0,0 +1,3 @@ +CitizenMP.Server.exe in bin/ is no longer use. + +Please run the executable from the root directory. \ No newline at end of file diff --git a/citmp-server.yml b/citmp-server.yml new file mode 100644 index 0000000..affed60 --- /dev/null +++ b/citmp-server.yml @@ -0,0 +1,40 @@ +ListenPort: 30120 + +PreParseResources: + - mapmanager + +AutoStartResources: + - chat + - spawnmanager + - fivem-map-skater + - baseevents + - rconlog + - hardcap # prevents too many players from joining + - scoreboard + +# do change +RconPassword: lovely + +# configure this, please +Hostname: default FiveReborn server + +# don't change this, it'll cause your server to be unlisted +Game: GTA5 + +PlatformServer: updater.fivereborn.com + +ScriptDebug: true + +DebugLog: true + +# set to false to hide from the online server list +Announce: true + +# set to false to require players to be signed in to Steam +DisableAuth: true + +# set to true to disable local script hook plugins +DisableScriptHook: false + +# set to the path of an icon to use in the server browser. it should be square. +#ServerIcon: hello.png \ No newline at end of file diff --git a/hello.png b/hello.png new file mode 100644 index 0000000..dcd524b Binary files /dev/null and b/hello.png differ diff --git a/libuv.dll b/libuv.dll new file mode 100644 index 0000000..5e02949 Binary files /dev/null and b/libuv.dll differ diff --git a/libuv.so b/libuv.so new file mode 100644 index 0000000..ee70aa0 Binary files /dev/null and b/libuv.so differ diff --git a/mono/System.Runtime.InteropServices.RuntimeInformation.dll b/mono/System.Runtime.InteropServices.RuntimeInformation.dll new file mode 100644 index 0000000..8ddc814 Binary files /dev/null and b/mono/System.Runtime.InteropServices.RuntimeInformation.dll differ diff --git a/mono/libSystem.Native.so b/mono/libSystem.Native.so new file mode 100644 index 0000000..16d40ce Binary files /dev/null and b/mono/libSystem.Native.so differ diff --git a/resources/[gamemodes]/race-test/__resource.lua b/resources/[gamemodes]/race-test/__resource.lua new file mode 100644 index 0000000..e137b4e --- /dev/null +++ b/resources/[gamemodes]/race-test/__resource.lua @@ -0,0 +1,3 @@ +resource_type 'map' { gameTypes = { race = true } } + +map 'map.lua' diff --git a/resources/[gamemodes]/race-test/map.lua b/resources/[gamemodes]/race-test/map.lua new file mode 100644 index 0000000..b9060ab --- /dev/null +++ b/resources/[gamemodes]/race-test/map.lua @@ -0,0 +1,31 @@ +spawnpoint 'ig_brucie' { 1127.99, -569.018, 12.5918, heading = 270 } +spawnpoint 'ig_brucie' { 1122.99, -569.018, 12.5918, heading = 270 } +spawnpoint 'ig_brucie' { 1119.99, -569.018, 12.5918, heading = 270 } +spawnpoint 'ig_brucie' { 1116.99, -569.018, 12.5918, heading = 270 } + +checkpoint { pos = { 1175.38, -568.361, 12.9386 } } +checkpoint { pos = { 1192.41, -484.373, 12.9902 } } +checkpoint { pos = { 1370.24, -454.326, 16.1487 } } +checkpoint { pos = { 1380.32, -319.673, 19.2937 } } +checkpoint { pos = { 1153.19, -334.017, 17.6384 } } +--[[checkpoint { pos = { 1120.66, -272.068, 19.6985 } } +checkpoint { pos = { 1014.76, -270.757, 20.9716 } } +checkpoint { pos = { 1018.3, -367.436, 18.9644 } } +checkpoint { pos = { 634.072, -400.678, 40.1211 } } +checkpoint { pos = { 218.425, -400.586, 14.506 } } +checkpoint { pos = { 27.9617, -408.632, 13.7693 } } +checkpoint { pos = { -0.9286, -625.767, 13.7056 } } +checkpoint { pos = { -114.801, -646.633, 13.8117 } } +checkpoint { pos = { -123.565, -785.028, 4.2556 } } +checkpoint { pos = { 114.514, -730.95, 4.0023 } } +checkpoint { pos = { 267.888, -668.781, 4.0181 } } +checkpoint { pos = { 337.305, -509.17, 3.7381 } } +checkpoint { pos = { 309.906, -407.719, 4.0468 } } +checkpoint { pos = { 296.932, -253.16, 3.9989 } } +checkpoint { pos = { 157.697, -234.079, 13.8108 } } +checkpoint { pos = { 140.173, -389.505, 13.7984 } } +checkpoint { pos = { 340.356, -415.681, 31.418 } } +checkpoint { pos = { 566.093, -415.848, 39.5434 } } +checkpoint { pos = { 909.641, -422.129, 36.4247 } } +checkpoint { pos = { 1049.37, -416.408, 17.3975 } } +checkpoint { pos = { 1091.72, -553.691, 12.5552 } }]] diff --git a/resources/[gamemodes]/race/__resource.lua b/resources/[gamemodes]/race/__resource.lua new file mode 100644 index 0000000..32440be --- /dev/null +++ b/resources/[gamemodes]/race/__resource.lua @@ -0,0 +1,9 @@ +resource_type 'gametype' { name = 'Race' } + +dependencies { + "spawnmanager", + "mapmanager" +} + +client_script 'race_client.lua' +server_script 'race_server.lua' diff --git a/resources/[gamemodes]/race/race_client.lua b/resources/[gamemodes]/race/race_client.lua new file mode 100644 index 0000000..691b16f --- /dev/null +++ b/resources/[gamemodes]/race/race_client.lua @@ -0,0 +1,469 @@ +local curCheckpoint, nextCheckpoint +local goGoGo + +local playerCar +local weFinished +local resultsShown + +local checkpoints = {} +local playerScores = {} + +local function initializeMap() + echo("[RACE] initializeMap\n") + + TriggerServerEvent('race:updateCheckpoints', checkpoints) +end + +local function resetGameMode() + curCheckpoint = nil + nextCheckpoint = nil + + SetMultiplayerHudTime('') + + checkpointCount = 0 + playerScores = {} + + if IsThisMachineTheServer() then + -- load the initial map + initializeMap() + end + + goGoGo = false + weFinished = false + resultsShown = false +end + +local function updatePositions() + local players = {} + + for id, data in pairs(playerScores) do + data.playerId = id + + table.insert(players, data) + end + + table.sort(players, function(a, b) + if a.finishPosition or b.finishPosition then + if not b.finishPosition then + return true + end + + if not a.finishPosition then + return false + end + + return a.finishPosition < b.finishPosition + end + + if a.cp == b.cp then + local aPed = a.ped + local bPed = b.ped + + local aPos + local bPos + + if not DoesCharExist(aPed) or not DoesCharExist(bPed) then + aPos = { 0, 0 } + bPos = { 0, 0 } + else + aPos = a.ped.position + bPos = b.ped.position + end + + local nextCp = checkpoints[a.cp + 1] + + if not nextCp then + return a.cp > b.cp + end + + local aDist = GetDistanceBetweenCoords2d(aPos[1], aPos[2], nextCp.pos[1], nextCp.pos[2]) + local bDist = GetDistanceBetweenCoords2d(bPos[1], bPos[2], nextCp.pos[1], nextCp.pos[2]) + + return aDist < bDist + end + + return a.cp > b.cp + end) + + if not playerScores[GetPlayerId().serverId] then + return + end + + local lastPosition = selfLastPosition + + local i = 1 + + for _, v in ipairs(players) do + playerScores[v.playerId].position = i + + i = i + 1 + end + + local selfPosition = playerScores[GetPlayerId().serverId].position + selfLastPosition = selfPosition + + if selfPosition ~= lastPosition then + TriggerEvent('chatMessage', '', { 0, 0, 0 }, 'position changed to ' .. tostring(selfPosition) .. ' from ' .. tostring(lastPosition)) + end + + + -- positions updated, we hope +end + +AddEventHandler('race:onPlayerFinished', function(player, data) + local selfId = GetPlayerId().serverId + + if not playerScores[player] then + local ped = sPlayer.ped + + playerScores[player] = { + cp = #checkpoints, + ped = ped, + vehicle = ped.vehicle + } + end + + playerScores[player].finishPosition = data.position + + if selfId == player then + exports.obituary:printObituary('New world record!') + + TriggerEvent('chatMessage', '', { 0, 0, 0 }, 'you finished!') + + weFinished = true + + tearDownCheckpoint(curCheckpoint) + tearDownCheckpoint(nextCheckpoint) + + -- todo: spectate? + + CreateThread(function() + Wait(500) + + if playerCar then + FreezeCarPosition(playerCar, true) + end + end) + end + + local sPlayer = GetPlayerByServerId(player) + + if sPlayer then + exports.obituary:printObituary('%s finished in %s seconds', sPlayer.name, tostring(data.finishSeconds)) + end +end) + +AddEventHandler('onClientGameTypeStart', function() + CreateThread(function() + --[[while true do + Wait(500) + + local player = GetPlayerId() + + TriggerServerEvent('race:updatePos', player.ped.position) + end]] + end) + + CreateThread(function() + while true do + Wait(250) + + updatePositions() + end + end) +end) + +function GetPlayerInteger(i) + local serverId = i.serverId + local players = GetPlayers() + + for k, v in ipairs(players) do + if v.serverId == serverId then + return k + end + end + + return 1 +end + +local function spawnVehicle(spawnPoint) + local carModel + + if not spawnPoint.carModel then + carModel = 'admiral' + else + carModel = spawnPoint.carModel + end + + if not tonumber(carModel) then + carModel = GetHashKey(carModel, _r) + end + + -- is the model actually a model? + if not IsModelInCdimage(carModel) then + error("invalid spawn model") + end + + -- is is even a vehicle? + if not IsThisModelAVehicle(carModel) then + error("this model ain't a vehicle!") + end + + -- spawn a vehicle for our lovely player + RequestModel(carModel) + LoadAllObjectsNow() + + playerCar = CreateCar(carModel, spawnPoint.x, spawnPoint.y, spawnPoint.z, 0, 1) + SetCarHeading(playerCar, spawnPoint.heading) + SetCarOnGroundProperly(playerCar) + + WarpCharIntoCar(GetPlayerId().ped, playerCar) + + if not goGoGo then + FreezeCarPosition(playerCar, true) + end + + LockCarDoors(playerCar, 4) + + -- and done, hopefully. +end + +AddEventHandler('race:itsGoTime', function() + if playerCar then + -- let go of the brakes + FreezeCarPosition(playerCar, false) + end + + -- gogogo + goGoGo = true +end) + +string.lpad = function(str, len, char) + if char == nil then char = ' ' end + return string.rep(char, len - #str) .. str +end + +AddEventHandler('race:results', function(time) + if playerCar then + FreezeCarPosition(playerCar, true) + end + + tearDownCheckpoint(curCheckpoint) + tearDownCheckpoint(nextCheckpoint) + + SetMultiplayerHudTime('') + + updatePositions() + + local players = {} + + for id, data in pairs(playerScores) do + table.insert(players, data) + end + + table.sort(players, function(a, b) return a.position < b.position end) + + TriggerEvent('chatMessage', '', { 0, 0, 0 }, 'RESULTS') + + for i, p in ipairs(players) do + local name = '**INVALID**' + local sp = GetPlayerByServerId(p.playerId) + + if sp then + name = sp.name + end + + TriggerEvent('chatMessage', '', { 0, 0, 0 }, tostring(i) .. '. ' .. name) + end +end) + +AddEventHandler('race:hurryUp', function(time) + CreateThread(function() + echo("resultsShown: " .. tostring(resultsShown) .. " , weF: " .. tostring(weFinished) .. "\n") + + while not resultsShown and not weFinished do + Wait(1000) + + time = time - 1000 + + SetMultiplayerHudTime('00:' .. tostring(math.floor(time / 1000)):lpad(2, '0')) + echo(tostring(math.floor(time / 1000)):lpad(2, '0') .. ':' .. tostring(math.floor((time % 1000) / 100)):lpad(2, '0') .. "\n") + end + end) +end) + +AddEventHandler('race:showGoMessage', function(message) + TriggerEvent('chatMessage', '', { 0, 0, 0 }, message) +end) + +AddEventHandler('onClientMapStart', function(res) + resetGameMode() + + requestedGo = true + + TriggerServerEvent('race:requestGo') +end) + +AddEventHandler('onClientMapStop', function(res) + DoScreenFadeOut(50) +end) + +AddEventHandler('race:weGotPorn', function() + echo("[RACE] race:weGotPorn\n") + + if not requestedGo then + return + end + + requestedGo = false + + exports.spawnmanager:setAutoSpawn(false) + + exports.spawnmanager:spawnPlayer(GetPlayerInteger(GetPlayerId()), function(spawnPoint) + spawnVehicle(spawnPoint) + end) + + TriggerServerEvent('race:requestCheckpoint', '1234') +end) + +local function setUpCheckpoint(cp, next) + local nextPos, typeNum + + if next then + nextPos = next.pos + typeNum = 2 + else + nextPos = { 0.0, 0.0, 0.0 } + typeNum = 3 + end + + -- 2 = regular 'ground', 3 = finish 'ground', others are different 3dmarker types + cp.handle = CreateCheckpoint(typeNum, cp.pos[1], cp.pos[2], cp.pos[3] + 2.5, nextPos[1], nextPos[2], nextPos[3], 1.0001, _r) + cp.blip = AddBlipForCoord(cp.pos[1], cp.pos[2], cp.pos[3], _i) + + if cp == nextCheckpoint then + ChangeBlipScale(cp.blip, 0.8) + end + + ChangeBlipSprite(cp.blip, 3) +end + +function tearDownCheckpoint(cp) + if not cp then + return + end + + if cp.blip then + RemoveBlip(cp.blip) + cp.blip = nil + end + + if cp.handle then + DeleteCheckpoint(cp.handle) + cp.handle = nil + end +end + +AddEventHandler('race:setCheckpoint', function(cur, next, later) + if curCheckpoint then + tearDownCheckpoint(curCheckpoint) + end + + if nextCheckpoint then + tearDownCheckpoint(nextCheckpoint) + end + + curCheckpoint = cur + nextCheckpoint = next + + if cur then + setUpCheckpoint(curCheckpoint, nextCheckpoint) + + -- make a background thread waiting for the checkpoint to be reached + CreateThread(function() + local localCur = curCheckpoint + + -- so we exit if the checkpoint target is changed + while curCheckpoint == localCur do + Wait(25) + + if playerCar then + local px, py, pz = GetCarCoordinates(playerCar) + local distance = GetDistanceBetweenCoords2d(px, py, localCur.pos[1], localCur.pos[2]) + + if distance < 10 then + -- pass the fact we reached the checkpoint to the server + TriggerServerEvent('race:gotCP', '1234') + + break + end + end + end + end) + end + + if next then + setUpCheckpoint(nextCheckpoint, later) + end +end) + +AddEventHandler('race:confirmCP', function() + PlayAudioEvent('FRONTEND_GAME_PICKUP_CHECKPOINT') +end) + +AddEventHandler('race:updateStatus', function(player, curCP) + if curCP == -1 then + playerScores[player] = nil + end + + local sPlayer = GetPlayerByServerId(player) + + if not sPlayer then + return + end + + local ped = sPlayer.ped + + playerScores[player] = { + cp = curCP, + ped = ped, + vehicle = ped.vehicle + } + + TriggerEvent('chatMessage', '', { 0, 0, 0 }, sPlayer.name .. ' now has cp ' .. curCP) + + updatePositions() +end) + +AddEventHandler('onClientMapStop', function() + if playerCar then + MarkCarAsNoLongerNeeded(playerCar) + playerCar = nil + end + + if curCheckpoint and curCheckpoint.handle then + DeleteCheckpoint(curCheckpoint.handle) + end + + if nextCheckpoint and nextCheckpoint.handle then + DeleteCheckpoint(nextCheckpoint.handle) + end +end) + +AddEventHandler('getMapDirectives', function(add) + -- call the remote callback + add('checkpoint', function(state, data) + table.insert(checkpoints, data) + + state.add('pos', data.pos) + + -- delete callback follows on the next line + end, function(state, arg) + for i, sp in ipairs(checkpoints) do + if sp.pos[1] == state.pos[1] and sp.pos[2] == state.pos[2] and sp.pos[3] == state.pos[3] then + table.remove(checkpoints, i) + return + end + end + end) +end) diff --git a/resources/[gamemodes]/race/race_server.lua b/resources/[gamemodes]/race/race_server.lua new file mode 100644 index 0000000..494243a --- /dev/null +++ b/resources/[gamemodes]/race/race_server.lua @@ -0,0 +1,209 @@ +local checkpoints = {} +local raceId = 0 + +RegisterServerEvent('race:updateCheckpoints') + +AddEventHandler('race:updateCheckpoints', function(cps) + if #checkpoints > 0 then + return + end + + checkpoints = cps + + TriggerClientEvent('race:weGotPorn', -1) +end) + +local playerData = {} + +local function ensurePlayerData(id) + if playerData[id] then + return + end + + playerData[id] = { + curCheckpoint = 0 + } +end + +local raceStarted = false +local playerCount = 0 + +local function startRace() + raceStarted = true + + print("really starting race") + + local function raceCountdown(num) + local time = (4000 - (num * 1000)) + + print("setting countdown for " .. tostring(time)) + + SetTimeout(time, function() + print("trig'd countdown for " .. tostring(time)) + + if num == 0 then + TriggerClientEvent('race:itsGoTime', -1, 0) + TriggerClientEvent('race:showGoMessage', -1, 'GO') + else + TriggerClientEvent('race:showGoMessage', -1, tostring(num)) + end + end) + end + + raceCountdown(3) -- 3... + raceCountdown(2) -- 2... + raceCountdown(1) -- 1... + raceCountdown(0) -- GOGOGO +end + +local function incrementPlayerCount() + playerCount = playerCount + 1 + + if playerCount > 4 then + startRace() + end + + if playerCount == 1 then + SetTimeout(3000, function() + if raceStarted then + return + end + + print("starting race") + + startRace() + end) + end +end + +local playersFinished +local raceEnded + +AddEventHandler('onMapStart', function() + playerCount = 0 + playersFinished = 0 + raceId = raceId + 1 + raceStarted = false + raceEnded = false + + playerData = {} + checkpoints = {} + + print("mmmmmm race") +end) + +local function endRace() + raceEnded = true + + TriggerClientEvent('race:results', -1, '1234') + + SetTimeout(7500, function() + TriggerEvent('mapmanager:roundEnded') + end) +end + +AddEventHandler('race:onPlayerFinished', function(player) + print(GetPlayerName(player) .. ' finished') + + local data = playerData[player] + local finishSeconds = os.clock() - data.startTime + + local position = playersFinished + 1 + data.position = position + playersFinished = position + + TriggerClientEvent('race:onPlayerFinished', -1, player, { + finishSeconds = finishSeconds, + position = position + }) + + if playersFinished == playerCount then + endRace() + elseif playersFinished == 1 then + local thisRaceId = raceId + + TriggerClientEvent('race:hurryUp', -1, 30000) + + SetTimeout(30000, function() + if raceId ~= thisRaceId or raceEnded then + return + end + + endRace() + end) + end +end) + +AddEventHandler('playerActivated', function() + if #checkpoints > 0 then + TriggerClientEvent('race:weGotPorn', source) + end +end) + +RegisterServerEvent('race:requestGo') + +AddEventHandler('race:requestGo', function() + if #checkpoints > 0 then + TriggerClientEvent('race:weGotPorn', source) + end +end) + +AddEventHandler('playerDropped', function(player) + if playerData[player] and playerData[player].curCheckpoint > 0 then + TriggerClientEvent('race:updateStatus', -1, player, -1) + + playerCount = playerCount - 1 + end +end) + +RegisterServerEvent('race:gotCP') + +AddEventHandler('race:gotCP', function() + ensurePlayerData(source) + + local data = playerData[source] + + local next = data.curCheckpoint + 1 + + if next > #checkpoints then + print("omg finished") + + TriggerEvent('race:onPlayerFinished', source) + + return + end + + data.curCheckpoint = next + + TriggerClientEvent('race:confirmCP', source) -- for sound effects + TriggerClientEvent('race:setCheckpoint', source, checkpoints[next], checkpoints[next + 1], checkpoints[next + 2]) + TriggerClientEvent('race:updateStatus', -1, source, next - 1) +end) + +RegisterServerEvent('race:requestCheckpoint') + +AddEventHandler('race:requestCheckpoint', function() + print('is it even in here') + + ensurePlayerData(source) + + print(source, 'requesting cp') + + if playerData[source].curCheckpoint == 0 then + incrementPlayerCount() + + print(source, 'requesting cp 0') + + local curCP = 1 + playerData[source].curCheckpoint = curCP + playerData[source].startTime = os.clock() + + TriggerClientEvent('race:setCheckpoint', source, checkpoints[curCP], checkpoints[curCP + 1], checkpoints[curCP + 2]) + TriggerClientEvent('race:updateStatus', -1, source, 0) + + -- should have raceReallyStarted since 4-second countdown + if raceStarted then + TriggerClientEvent('race:itsGoTime', -1, 0) + end + end +end) diff --git a/resources/[gameplay]/channelfeed/__resource.lua b/resources/[gameplay]/channelfeed/__resource.lua new file mode 100644 index 0000000..82a4df1 --- /dev/null +++ b/resources/[gameplay]/channelfeed/__resource.lua @@ -0,0 +1,18 @@ +description 'output lists for game content' + +SetResourceInfo('uiPage', 'client/html/index.html') + +client_script 'client/channelfeed.lua' + +export 'printTo' +export 'addChannel' +export 'removeChannel' + +files +{ + 'client/html/index.html', + 'client/html/feed.js', + 'client/html/feed.css', + 'client/fonts/roboto-regular.ttf', + 'client/fonts/roboto-condensed.ttf', +} diff --git a/resources/[gameplay]/channelfeed/client/channelfeed.lua b/resources/[gameplay]/channelfeed/client/channelfeed.lua new file mode 100644 index 0000000..5a20a15 --- /dev/null +++ b/resources/[gameplay]/channelfeed/client/channelfeed.lua @@ -0,0 +1,40 @@ +local eventBuffer = {} + +AddUIHandler('getNew', function(data, cb) + local localBuf = eventBuffer + eventBuffer = {} + + cb(localBuf) +end) + +function printTo(channel, data) + table.insert(eventBuffer, { + meta = 'print', + channel = channel, + data = data + }) + + PollUI() +end + +function addChannel(id, options) + if not options.template then + return + end + + options.id = id + + table.insert(eventBuffer, { + meta = 'addChannel', + data = options + }) + + PollUI() +end + +function removeChannel(id) + table.insert(eventBuffer, { + meta = 'removeChannel', + data = id + }) +end diff --git a/resources/[gameplay]/channelfeed/client/fonts/roboto-condensed.ttf b/resources/[gameplay]/channelfeed/client/fonts/roboto-condensed.ttf new file mode 100644 index 0000000..57359d0 Binary files /dev/null and b/resources/[gameplay]/channelfeed/client/fonts/roboto-condensed.ttf differ diff --git a/resources/[gameplay]/channelfeed/client/fonts/roboto-regular.ttf b/resources/[gameplay]/channelfeed/client/fonts/roboto-regular.ttf new file mode 100644 index 0000000..305f0d5 Binary files /dev/null and b/resources/[gameplay]/channelfeed/client/fonts/roboto-regular.ttf differ diff --git a/resources/[gameplay]/channelfeed/client/html/feed.js b/resources/[gameplay]/channelfeed/client/html/feed.js new file mode 100644 index 0000000..8ee2ced --- /dev/null +++ b/resources/[gameplay]/channelfeed/client/html/feed.js @@ -0,0 +1,118 @@ +(function() { + var getLock = 0; + + var channels = {}; + + var zoomLevel = '100%'; + + $(function() + { + zoomLevel = Math.round(($(window).height() / 720) * 100) + '%'; // yay dynamic typing + }); + + function refetchData() + { + getLock = 0; + + $.get('http://channelfeed/getNew', function(data) + { + if (getLock > 1) + { + setTimeout(refetchData, 50); + + return; + } + + getLock++; + + data.forEach(function(item) + { + switch (item.meta) + { + case 'print': + var channel = item.channel; + + if (!(channel in channels)) + { + return; + } + + channel = channels[channel]; + + var elem = $($.Mustache.render(item.channel, item.data, { method: channel.method })).appendTo(channel.$elem); + + setTimeout(function() + { + elem.fadeOut(400, function() + { + elem.remove(); + }); + }, 7500); + + break; + + case 'addChannel': + var channel = item.data; + + if (channel.id in channels) + { + return; + } + + channel.$elem = $('
').attr('id', 'channel-' + channel.id).appendTo('#channels'); + + if (channel.styles !== undefined) + { + channel.$elem.css(channel.styles); + } + + channel.$elem = $('
').css('zoom', zoomLevel).appendTo(channel.$elem); + + if (channel.styleUrl !== undefined) + { + $('').appendTo('head').attr({ type: 'text/css', rel: 'stylesheet' }).attr('href', channel.styleUrl); + } + + $.Mustache.add(channel.id, channel.template); + + channels[channel.id] = channel; + + break; + case 'removeChannel': + var channelId = item.data; + + if (channelId in channels) + { + channel.$elem.parent().remove(); + + delete channels[channelId]; + } + + break; + case 'clear': + var channel = item.channel; + + if (!(channel in channels)) + { + return; + } + + channel = channels[channel]; + + channel.$elem.html(); + break; + } + }); + }); + } + + window.addEventListener('message', function(event) + { + if (event.data.type != 'poll') + { + return; + } + + refetchData(); + }); +})(); diff --git a/resources/[gameplay]/channelfeed/client/html/index.html b/resources/[gameplay]/channelfeed/client/html/index.html new file mode 100644 index 0000000..0e08750 --- /dev/null +++ b/resources/[gameplay]/channelfeed/client/html/index.html @@ -0,0 +1,13 @@ + + + + + + + + + +
+
+ + diff --git a/resources/[gameplay]/irc/ChatSharp.dll b/resources/[gameplay]/irc/ChatSharp.dll new file mode 100644 index 0000000..27d8b34 Binary files /dev/null and b/resources/[gameplay]/irc/ChatSharp.dll differ diff --git a/resources/[gameplay]/irc/__resource.lua b/resources/[gameplay]/irc/__resource.lua new file mode 100644 index 0000000..99dab82 --- /dev/null +++ b/resources/[gameplay]/irc/__resource.lua @@ -0,0 +1 @@ +server_script 'irc.lua' \ No newline at end of file diff --git a/resources/[gameplay]/irc/irc.lua b/resources/[gameplay]/irc/irc.lua new file mode 100644 index 0000000..43471ec --- /dev/null +++ b/resources/[gameplay]/irc/irc.lua @@ -0,0 +1,4 @@ +local reflection = clr.System.Reflection +local assembly = reflection.Assembly.LoadFrom('resources/[gameplay]/irc/ChatSharp.dll') + +dofile('resources/[gameplay]/irc/irc_run.lua') \ No newline at end of file diff --git a/resources/[gameplay]/irc/irc_run.lua b/resources/[gameplay]/irc/irc_run.lua new file mode 100644 index 0000000..ad8df62 --- /dev/null +++ b/resources/[gameplay]/irc/irc_run.lua @@ -0,0 +1,91 @@ +local chatSharp = clr.ChatSharp + +local client = chatSharp.IrcClient('irc.rizon.net', chatSharp.IrcUser('citimate', 'mateyate'), false) + +-- temporary workaround for connections that never triggered playerActivated but triggered playerDropped +local activatedPlayers = {} + +client.ConnectionComplete:add(function(s : object, e : System.EventArgs) : void + client:JoinChannel('#meow') +end) + +-- why is 'received' even misspelled here? +client.ChannelMessageRecieved:add(function(s : object, e : ChatSharp.Events.PrivateMessageEventArgs) : void + local msg = e.PrivateMessage + + TriggerClientEvent('chatMessage', -1, msg.User.Nick, { 0, 0x99, 255 }, msg.Message) +end) + +AddEventHandler('playerActivated', function() + client:SendMessage('* ' .. GetPlayerName(source) .. '(' .. GetPlayerGuid(source) .. '@' .. GetPlayerEP(source) .. ') joined the server', '#fourdeltaone') + table.insert(activatedPlayers, GetPlayerGuid(source)) +end) + +AddEventHandler('playerDropped', function() + -- find out if this connection ever triggered playerActivated + for index,guid in pairs(activatedPlayers) do + if guid == playerGuid then + -- show player dropping connection in chat + client:SendMessage('* ' .. GetPlayerName(source) .. '(' .. GetPlayerGuid(source) .. '@' .. GetPlayerEP(source) .. ') left the server', '#fourdeltaone') + table.remove(activatedPlayers, index) + return + end + end +end) + +AddEventHandler('chatMessage', function(source, name, message) + print('hey there ' .. name) + + local displayMessage = gsub(message, '^%d', '') + + -- ignore zero-length messages + if string.len(displayMessage) == 0 then + return + end + + -- ignore chat messages that are actually commands + if string.sub(displayMessage, 1, 1) == "/" then + return + end + + client:SendMessage('[' .. tostring(GetPlayerName(source)) .. ']: ' .. displayMessage, '#fourdeltaone') +end) + +AddEventHandler('onPlayerKilled', function(playerId, attackerId, reason, position) + local player = GetPlayerByServerId(playerId) + local attacker = GetPlayerByServerId(attackerId) + + local reasonString = 'killed' + + if reason == 0 or reason == 56 or reason == 1 or reason == 2 then + reasonString = 'meleed' + elseif reason == 3 then + reasonString = 'knifed' + elseif reason == 4 or reason == 6 or reason == 18 or reason == 51 then + reasonString = 'bombed' + elseif reason == 5 or reason == 19 then + reasonString = 'burned' + elseif reason == 7 or reason == 9 then + reasonString = 'pistoled' + elseif reason == 10 or reason == 11 then + reasonString = 'shotgunned' + elseif reason == 12 or reason == 13 or reason == 52 then + reasonString = 'SMGd' + elseif reason == 14 or reason == 15 or reason == 20 then + reasonString = 'assaulted' + elseif reason == 16 or reason == 17 then + reasonString = 'sniped' + elseif reason == 49 or reason == 50 then + reasonString = 'ran over' + end + + client:SendMessage('* ' .. attacker.name .. ' ' .. reasonString .. ' ' .. player.name, '#fourdeltaone') +end) + +client:ConnectAsync() + +AddEventHandler('onResourceStop', function(name) + if name == GetInvokingResource() then + client:Quit('Resource stopping.') + end +end) \ No newline at end of file diff --git a/resources/[gameplay]/obituary-deaths/__resource.lua b/resources/[gameplay]/obituary-deaths/__resource.lua new file mode 100644 index 0000000..e16f65e --- /dev/null +++ b/resources/[gameplay]/obituary-deaths/__resource.lua @@ -0,0 +1,5 @@ +dependency 'obituary' + +description 'death messages using the obituary resource' + +client_script 'deathmessages.lua' diff --git a/resources/[gameplay]/obituary-deaths/deathmessages.lua b/resources/[gameplay]/obituary-deaths/deathmessages.lua new file mode 100644 index 0000000..635bb1b --- /dev/null +++ b/resources/[gameplay]/obituary-deaths/deathmessages.lua @@ -0,0 +1,42 @@ +AddEventHandler('onPlayerDied', function(playerId, reason, position) + local player = GetPlayerByServerId(playerId) + + if player then + exports.obituary:printObituary('%s died.', player.name) + end +end) + +AddEventHandler('onPlayerKilled', function(playerId, attackerId, reason, position) + local player = GetPlayerByServerId(playerId) + local attacker = GetPlayerByServerId(attackerId) + + local reasonString = 'killed' + + if reason == 0 or reason == 56 or reason == 1 or reason == 2 then + reasonString = 'meleed' + elseif reason == 3 then + reasonString = 'knifed' + elseif reason == 4 or reason == 6 or reason == 18 or reason == 51 then + reasonString = 'bombed' + elseif reason == 5 or reason == 19 then + reasonString = 'burned' + elseif reason == 7 or reason == 9 then + reasonString = 'pistoled' + elseif reason == 10 or reason == 11 then + reasonString = 'shotgunned' + elseif reason == 12 or reason == 13 or reason == 52 then + reasonString = 'SMGd' + elseif reason == 14 or reason == 15 or reason == 20 then + reasonString = 'assaulted' + elseif reason == 16 or reason == 17 then + reasonString = 'sniped' + elseif reason == 49 or reason == 50 then + reasonString = 'ran over' + end + + echo("obituary-deaths: onPlayerKilled\n") + + if player and attacker then + exports.obituary:printObituary('%s %s %s.', attacker.name, reasonString, player.name) + end +end) diff --git a/resources/[gameplay]/obituary/__resource.lua b/resources/[gameplay]/obituary/__resource.lua new file mode 100644 index 0000000..ecbb218 --- /dev/null +++ b/resources/[gameplay]/obituary/__resource.lua @@ -0,0 +1,7 @@ +dependency 'channelfeed' + +client_script 'obituary.lua' + +export 'printObituary' + +files 'obituary.css' diff --git a/resources/[gameplay]/obituary/obituary.css b/resources/[gameplay]/obituary/obituary.css new file mode 100644 index 0000000..4f8b00f --- /dev/null +++ b/resources/[gameplay]/obituary/obituary.css @@ -0,0 +1,46 @@ +@font-face { + font-family: 'Roboto Condensed'; + src: url('nui://channelfeed/client/fonts/roboto-condensed.ttf') format('truetype'); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: 'Roboto'; + src: url('nui://channelfeed/client/fonts/roboto-regular.ttf') format('truetype'); + font-weight: normal; + font-style: normal; +} + +#channel-obituary +{ + position: absolute; +} + +#channel-obituary > div +{ + width: 100%; + + font-family: 'Roboto'; +} + +#channel-obituary div.item +{ + background-color: rgba(50, 50, 50, .6); + padding: 4px; + padding-left: 8px; + padding-right: 8px; + border-radius: 4px; + color: #eee; + margin-bottom: 3px; + + font-size: 65%; +} + +#channel-obituary div.item b +{ + font-family: 'Roboto Condensed'; + font-size: 125%; + font-weight: normal; + color: #09f; +} diff --git a/resources/[gameplay]/obituary/obituary.lua b/resources/[gameplay]/obituary/obituary.lua new file mode 100644 index 0000000..24469d4 --- /dev/null +++ b/resources/[gameplay]/obituary/obituary.lua @@ -0,0 +1,51 @@ +AddEventHandler('onClientResourceStart', function(name) + if name == GetCurrentResource() then + local x, y = GetHudPosition('HUD_RADAR') + local w, h = GetHudSize('HUD_RADAR') + + x = x - 0.01 + w = w + 0.02 + + if GetIsWidescreen() then + x = x / 1.333 + w = w / 1.333 + end + + exports.channelfeed:addChannel('obituary', { + method = 'append', + styleUrl = 'nui://obituary/obituary.css', + styles = { -- temporary + left = tostring(x * 100) .. '%', + bottom = 'calc(' .. tostring((1 - y) * 100) .. '% + 10px)', + width = tostring(w * 100) .. '%' + }, + template = '
{{{text}}}
' + }) + end +end) + +function printObituary(format, ...) + local args = table.pack(...) + + for i = 1, args.n do + if type(args[i]) == 'string' then + args[i] = args[i]:gsub('<', '<') + end + end + + echo("obituary: printObituary\n") + + exports.channelfeed:printTo('obituary', { + text = string.format(format, table.unpack(args)) + }) +end + +--[[AddEventHandler('chatMessage', function(name, color, message) + exports.channelfeed:printTo('obituary', { + text = message:gsub('<', '<') + }) +end)]] + +AddEventHandler('onClientResourceStop', function() + -- todo: remove channel +end) diff --git a/resources/[managers]/mapmanager/__resource.lua b/resources/[managers]/mapmanager/__resource.lua new file mode 100644 index 0000000..8d6655c --- /dev/null +++ b/resources/[managers]/mapmanager/__resource.lua @@ -0,0 +1,9 @@ +client_scripts { + "mapmanager_client.lua", + "mapmanager_shared.lua" +} + +server_scripts { + "mapmanager_server.lua", + "mapmanager_shared.lua" +} diff --git a/resources/[managers]/mapmanager/mapmanager_client.lua b/resources/[managers]/mapmanager/mapmanager_client.lua new file mode 100644 index 0000000..515728b --- /dev/null +++ b/resources/[managers]/mapmanager/mapmanager_client.lua @@ -0,0 +1,200 @@ +maps = {} +gametypes = {} + +AddEventHandler('getResourceInitFuncs', function(isPreParse, add) + if not isPreParse then + add('map', function(file) + addMap(file, GetInvokingResource()) + end) + + add('resource_type', function(type) + return function(params) + local resourceName = GetInvokingResource() + + if type == 'map' then + maps[resourceName] = params + elseif type == 'gametype' then + gametypes[resourceName] = params + end + end + end) + end +end) + +mapFiles = {} + +function addMap(file, owningResource) + if not mapFiles[owningResource] then + mapFiles[owningResource] = {} + end + + table.insert(mapFiles[owningResource], file) +end + +AddEventHandler('onClientResourceStart', function(res) + -- parse metadata for this resource + + -- map files + local num = GetNumResourceMetadata(res, 'map') + + if num then + for i = 0, num-1 do + local file = GetResourceMetadata(res, 'map', i) + + if file then + addMap(file, res) + end + end + end + + -- resource type data + local type = GetResourceMetadata(res, 'resource_type', 0) + + if type then + Citizen.Trace("type " .. res .. " " .. type .. "\n") + + local extraData = GetResourceMetadata(res, 'resource_type_extra', 0) + + if extraData then + extraData = json.decode(extraData) + else + extraData = {} + end + + if type == 'map' then + maps[res] = extraData + elseif type == 'gametype' then + gametypes[res] = extraData + end + end + + -- handle starting + if mapFiles[res] then + for _, file in ipairs(mapFiles[res]) do + parseMap(file, res) + end + end + + -- defer this to the next game tick to work around a lack of dependencies + Citizen.CreateThread(function() + Citizen.Wait(15) + + if maps[res] then + TriggerEvent('onClientMapStart', res) + elseif gametypes[res] then + TriggerEvent('onClientGameTypeStart', res) + end + end) +end) + +AddEventHandler('onClientResourceStop', function(res) + if maps[res] then + TriggerEvent('onClientMapStop', res) + elseif gametypes[res] then + TriggerEvent('onClientGameTypeStop', res) + end + + if undoCallbacks[res] then + for _, cb in ipairs(undoCallbacks[res]) do + cb() + end + + undoCallbacks[res] = nil + mapFiles[res] = nil + end +end) + +undoCallbacks = {} + +function parseMap(file, owningResource) + if not undoCallbacks[owningResource] then + undoCallbacks[owningResource] = {} + end + + local env = { + math = math, pairs = pairs, ipairs = ipairs, next = next, tonumber = tonumber, tostring = tostring, + type = type, table = table, string = string, _G = env + } + + TriggerEvent('getMapDirectives', function(key, cb, undocb) + env[key] = function(...) + local state = {} + + state.add = function(k, v) + state[k] = v + end + + local result = cb(state, ...) + local args = table.pack(...) + + table.insert(undoCallbacks[owningResource], function() + undocb(state) + end) + + return result + end + end) + + local mt = { + __index = function(t, k) + if rawget(t, k) ~= nil then return rawget(t, k) end + + -- as we're not going to return nothing here (to allow unknown directives to be ignored) + local f = function() + return f + end + + return function() return f end + end + } + + setmetatable(env, mt) + + local fileData = LoadResourceFile(owningResource, file) + local mapFunction, err = load(fileData, file, 't', env) + + if not mapFunction then + Citizen.Trace("Couldn't load map " .. file .. ": " .. err .. " (type of fileData: " .. type(fileData) .. ")\n") + return + end + + mapFunction() +end + +AddEventHandler('getMapDirectives', function(add) + add('vehicle_generator', function(state, name) + return function(opts) + local x, y, z, heading + local color1, color2 + + if opts.x then + x = opts.x + y = opts.y + z = opts.z + else + x = opts[1] + y = opts[2] + z = opts[3] + end + + heading = opts.heading or 1.0 + color1 = opts.color1 or -1 + color2 = opts.color2 or -1 + + local hash = GetHashKey(name) + RequestModel(hash) + + LoadAllObjectsNow() + + local carGen = CreateScriptVehicleGenerator(x, y, z, heading, 5.0, 3.0, hash, color1, color2, -1, -1, true, false, false, true, true, -1) + SetScriptVehicleGenerator(carGen, true) + SetAllVehicleGeneratorsActive(true) + + state.add('cargen', carGen) + end + end, function(state, arg) + Citizen.Trace("deleting car gen " .. tostring(state.cargen) .. "\n") + + DeleteScriptVehicleGenerator(state.cargen) + end) +end) diff --git a/resources/[managers]/mapmanager/mapmanager_server.lua b/resources/[managers]/mapmanager/mapmanager_server.lua new file mode 100644 index 0000000..c7e4599 --- /dev/null +++ b/resources/[managers]/mapmanager/mapmanager_server.lua @@ -0,0 +1,273 @@ +-- loosely based on MTA's https://code.google.com/p/mtasa-resources/source/browse/trunk/%5Bmanagers%5D/mapmanager/mapmanager_main.lua + +maps = {} +gametypes = {} + +AddEventHandler('getResourceInitFuncs', function(isPreParse, add) + add('resource_type', function(type) + return function(params) + local resourceName = GetInvokingResource() + + if type == 'map' then + maps[resourceName] = params + elseif type == 'gametype' then + gametypes[resourceName] = params + end + end + end) + + add('map', function(file) + AddAuxFile(file) + end) +end) + +AddEventHandler('onResourceStarting', function(resource) + if maps[resource] then + if getCurrentMap() and getCurrentMap() ~= resource then + if doesMapSupportGameType(getCurrentGameType(), resource) then + print("Changing map from " .. getCurrentMap() .. " to " .. resource) + + changeMap(resource) + else + -- check if there's only one possible game type for the map + local map = maps[resource] + local count = 0 + local gt + + for type, flag in pairs(map.gameTypes) do + if flag then + count = count + 1 + gt = type + end + end + + if count == 1 then + print("Changing map from " .. getCurrentMap() .. " to " .. resource .. " (gt " .. gt .. ")") + + changeGameType(gt) + changeMap(resource) + end + end + + CancelEvent() + end + elseif gametypes[resource] then + if getCurrentGameType() and getCurrentGameType() ~= resource then + print("Changing gametype from " .. getCurrentGameType() .. " to " .. resource) + + changeGameType(resource) + + CancelEvent() + end + end +end) + +math.randomseed(GetInstanceId()) + +local currentGameType = nil +local currentMap = nil + +AddEventHandler('onResourceStart', function(resource) + if maps[resource] then + if not getCurrentGameType() then + for gt, _ in pairs(maps[resource].gameTypes) do + changeGameType(gt) + break + end + end + + if getCurrentGameType() and not getCurrentMap() then + if doesMapSupportGameType(currentGameType, resource) then + if TriggerEvent('onMapStart', resource, maps[resource]) then + if maps[resource].name then + SetMapName(maps[resource].name) + else + SetMapName(resource) + end + + currentMap = resource + else + currentMap = nil + end + end + end + elseif gametypes[resource] then + if not getCurrentGameType() then + if TriggerEvent('onGameTypeStart', resource, gametypes[resource]) then + currentGameType = resource + + local gtName = gametypes[resource].name or resource + + SetGameType(gtName) + + print('Started gametype ' .. gtName) + TriggerClientEvent('onClientGameTypeStart', -1, getCurrentGameType()) + + SetTimeout(50, function() + if not currentMap then + local possibleMaps = {} + + for map, data in pairs(maps) do + if data.gameTypes[currentGameType] then + table.insert(possibleMaps, map) + end + end + + if #possibleMaps > 0 then + local rnd = math.random(#possibleMaps) + changeMap(possibleMaps[rnd]) + end + end + end) + else + currentGameType = nil + end + end + end +end) + +local function handleRoundEnd() + local possibleMaps = {} + + for map, data in pairs(maps) do + if data.gameTypes[currentGameType] then + table.insert(possibleMaps, map) + end + end + + if #possibleMaps > 0 then + local rnd = math.random(#possibleMaps) + changeMap(possibleMaps[rnd]) + end +end + +AddEventHandler('mapmanager:roundEnded', function() + -- set a timeout as we don't want to return to a dead environment + SetTimeout(50, handleRoundEnd) -- not a closure as to work around some issue in neolua? +end) + +AddEventHandler('onResourceStop', function(resource) + if resource == currentGameType then + TriggerEvent('onGameTypeStop', resource) + + currentGameType = nil + + if currentMap then + StopResource(currentMap) + end + elseif resource == currentMap then + TriggerEvent('onMapStop', resource) + + currentMap = nil + end +end) + +AddEventHandler('rconCommand', function(commandName, args) + if commandName == 'map' then + if #args ~= 1 then + RconPrint("usage: map [mapname]\n") + end + + if not maps[args[1]] then + RconPrint('no such map ' .. args[1] .. "\n") + CancelEvent() + + return + end + + if currentGameType == nil or not doesMapSupportGameType(currentGameType, args[1]) then + local map = maps[args[1]] + local count = 0 + local gt + + for type, flag in pairs(map.gameTypes) do + if flag then + count = count + 1 + gt = type + end + end + + if count == 1 then + print("Changing map from " .. getCurrentMap() .. " to " .. args[1] .. " (gt " .. gt .. ")") + + changeGameType(gt) + changeMap(args[1]) + + RconPrint('map ' .. args[1] .. "\n") + else + RconPrint('map ' .. args[1] .. ' does not support ' .. currentGameType .. "\n") + end + + CancelEvent() + + return + end + + changeMap(args[1]) + + RconPrint('map ' .. args[1] .. "\n") + + CancelEvent() + elseif commandName == 'gametype' then + if #args ~= 1 then + RconPrint("usage: gametype [name]\n") + end + + if not gametypes[args[1]] then + RconPrint('no such gametype ' .. args[1] .. "\n") + CancelEvent() + + return + end + + changeGameType(args[1]) + + RconPrint('gametype ' .. args[1] .. "\n") + + CancelEvent() + end +end) + +function getCurrentGameType() + return currentGameType +end + +function getCurrentMap() + return currentMap +end + +function changeGameType(gameType) + if currentMap and not doesMapSupportGameType(gameType, currentMap) then + StopResource(currentMap) + end + + if currentGameType then + StopResource(currentGameType) + end + + StartResource(gameType) +end + +function changeMap(map) + if currentMap then + StopResource(currentMap) + end + + StartResource(map) +end + +function doesMapSupportGameType(gameType, map) + if not gametypes[gameType] then + return false + end + + if not maps[map] then + return false + end + + if not maps[map].gameTypes then + return true + end + + return maps[map].gameTypes[gameType] +end diff --git a/resources/[managers]/mapmanager/mapmanager_shared.lua b/resources/[managers]/mapmanager/mapmanager_shared.lua new file mode 100644 index 0000000..7d5f180 --- /dev/null +++ b/resources/[managers]/mapmanager/mapmanager_shared.lua @@ -0,0 +1 @@ +-- shared logic file for map manager - don't call any subsystem-specific functions here diff --git a/resources/[system]/baseevents/__resource.lua b/resources/[system]/baseevents/__resource.lua new file mode 100644 index 0000000..f6929be --- /dev/null +++ b/resources/[system]/baseevents/__resource.lua @@ -0,0 +1,2 @@ +client_script 'deathevents.lua' +client_script 'vehiclechecker.lua' \ No newline at end of file diff --git a/resources/[system]/baseevents/deathevents.lua b/resources/[system]/baseevents/deathevents.lua new file mode 100644 index 0000000..d87871c --- /dev/null +++ b/resources/[system]/baseevents/deathevents.lua @@ -0,0 +1,73 @@ +Citizen.CreateThread(function() + local isDead = false + local hasBeenDead = false + local diedAt + + while true do + Wait(0) + + local player = PlayerId() + + if NetworkIsPlayerActive(player) then + local ped = PlayerPedId() + + if IsPedFatallyInjured(ped) and not isDead then + isDead = true + if not diedAt then + diedAt = GetGameTimer() + end + + local killer = NetworkGetEntityKillerOfPlayer(player) + local killerentitytype = GetEntityType(killer) + local killertype = -1 + local killerinvehicle = false + local killervehiclename = '' + local killervehicleseat = 0 + if killerentitytype == 1 then + killertype = GetPedType(killer) + if IsPedInAnyVehicle(killer, false) == 1 then + killerinvehicle = true + killervehiclename = GetDisplayNameFromVehicleModel(GetEntityModel(GetVehiclePedIsUsing(killer))) + killervehicleseat = GetPedVehicleSeat(killer) + else killerinvehicle = false + end + end + + local killerid = GetPlayerByEntityID(killer) + if killer ~= ped and killerid ~= nil and NetworkIsPlayerActive(killerid) then killerid = GetPlayerServerId(killerid) + else killerid = -1 + end + + if killer == ped then + TriggerEvent('baseevents:onPlayerDied', killertype, { table.unpack(GetEntityCoords(ped)) }) + TriggerServerEvent('baseevents:onPlayerDied', killertype, { table.unpack(GetEntityCoords(ped)) }) + hasBeenDead = true + else + TriggerEvent('baseevents:onPlayerKilled', killerid, {killertype=killertype, killerinveh=killerinvehicle, killervehseat=killervehicleseat, killervehname=killervehiclename, killerpos=table.unpack(GetEntityCoords(ped))}) + TriggerServerEvent('baseevents:onPlayerKilled', killerid, {killertype=killertype, killerinveh=killerinvehicle, killervehseat=killervehicleseat, killervehname=killervehiclename, killerpos=table.unpack(GetEntityCoords(ped))}) + hasBeenDead = true + end + elseif not IsPedFatallyInjured(ped) then + isDead = false + diedAt = nil + end + + -- check if the player has to respawn in order to trigger an event + if not hasBeenDead and diedAt ~= nil and diedAt > 0 then + TriggerEvent('baseevents:onPlayerWasted', { table.unpack(GetEntityCoords(ped)) }) + TriggerServerEvent('baseevents:onPlayerWasted', { table.unpack(GetEntityCoords(ped)) }) + + hasBeenDead = true + elseif hasBeenDead and diedAt ~= nil and diedAt <= 0 then + hasBeenDead = false + 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 \ No newline at end of file diff --git a/resources/[system]/baseevents/vehiclechecker.lua b/resources/[system]/baseevents/vehiclechecker.lua new file mode 100644 index 0000000..8edea38 --- /dev/null +++ b/resources/[system]/baseevents/vehiclechecker.lua @@ -0,0 +1,54 @@ +local isInVehicle = false +local isEnteringVehicle = false +local currentVehicle = 0 +local currentSeat = 0 + +Citizen.CreateThread(function() + while true do + Citizen.Wait(0) + + local ped = PlayerPedId() + + if not isInVehicle and not IsPlayerDead(PlayerId()) then + if DoesEntityExist(GetVehiclePedIsTryingToEnter(ped)) and not isEnteringVehicle then + -- trying to enter a vehicle! + local vehicle = GetVehiclePedIsTryingToEnter(ped) + local seat = GetSeatPedIsTryingToEnter(ped) + isEnteringVehicle = true + TriggerServerEvent('baseevents:enteringVehicle', vehicle, seat, GetDisplayNameFromVehicleModel(GetEntityModel(vehicle))) + elseif not DoesEntityExist(GetVehiclePedIsTryingToEnter(ped)) and not IsPedInAnyVehicle(ped, true) and isEnteringVehicle then + -- vehicle entering aborted + TriggerServerEvent('baseevents:enteringAborted') + isEnteringVehicle = false + elseif IsPedInAnyVehicle(ped, false) then + -- suddenly appeared in a vehicle, possible teleport + isEnteringVehicle = false + isInVehicle = true + currentVehicle = GetVehiclePedIsUsing(ped) + currentSeat = GetPedVehicleSeat(ped) + local model = GetEntityModel(currentVehicle) + local name = GetDisplayNameFromVehicleModel() + TriggerServerEvent('baseevents:enteredVehicle', currentVehicle, currentSeat, GetDisplayNameFromVehicleModel(GetEntityModel(currentVehicle))) + end + elseif isInVehicle then + if not IsPedInAnyVehicle(ped, false) or IsPlayerDead(PlayerId()) then + -- bye, vehicle + local model = GetEntityModel(currentVehicle) + local name = GetDisplayNameFromVehicleModel() + TriggerServerEvent('baseevents:leftVehicle', currentVehicle, currentSeat, GetDisplayNameFromVehicleModel(GetEntityModel(currentVehicle))) + isInVehicle = false + currentVehicle = 0 + currentSeat = 0 + end + end + Citizen.Wait(50) + end +end) + +function GetPedVehicleSeat(ped) + local vehicle = GetVehiclePedIsIn(ped, false) + for i=-2,GetVehicleMaxNumberOfPassengers(vehicle) do + if(GetPedInVehicleSeat(vehicle, i) == ped) then return i end + end + return -2 +end \ No newline at end of file diff --git a/resources/[system]/chat/__resource.lua b/resources/[system]/chat/__resource.lua new file mode 100644 index 0000000..1d9b249 --- /dev/null +++ b/resources/[system]/chat/__resource.lua @@ -0,0 +1,15 @@ +description 'chat management stuff' + +ui_page 'html/chat.html' + +client_script 'chat_client.lua' +server_script 'chat_server.lua' + +export 'printChatLine' + +files { + 'html/chat.html', + 'html/chat.css', + 'html/chat.js', + 'html/jquery.faketextbox.js' +} diff --git a/resources/[system]/chat/chat_client.lua b/resources/[system]/chat/chat_client.lua new file mode 100644 index 0000000..4be4f2f --- /dev/null +++ b/resources/[system]/chat/chat_client.lua @@ -0,0 +1,56 @@ +local chatInputActive = false +local chatInputActivating = false + +RegisterNetEvent('chatMessage') + +AddEventHandler('chatMessage', function(name, color, message) + SendNUIMessage({ + name = name, + color = color, + message = message + }) +end) + +RegisterNUICallback('chatResult', function(data, cb) + chatInputActive = false + + SetNuiFocus(false) + + if data.message then + local id = PlayerId() + + --local r, g, b = GetPlayerRgbColour(id, _i, _i, _i) + local r, g, b = 0, 0x99, 255 + + TriggerServerEvent('chatMessageEntered', GetPlayerName(id), { r, g, b }, data.message) + end + + cb('ok') +end) + +Citizen.CreateThread(function() + SetTextChatEnabled(false) + + while true do + Wait(0) + + if not chatInputActive then + if IsControlPressed(0, 245) --[[ INPUT_MP_TEXT_CHAT_ALL ]] then + chatInputActive = true + chatInputActivating = true + + SendNUIMessage({ + meta = 'openChatBox' + }) + end + end + + if chatInputActivating then + if not IsControlPressed(0, 245) then + SetNuiFocus(true) + + chatInputActivating = false + end + end + end +end) diff --git a/resources/[system]/chat/chat_server.lua b/resources/[system]/chat/chat_server.lua new file mode 100644 index 0000000..28370ed --- /dev/null +++ b/resources/[system]/chat/chat_server.lua @@ -0,0 +1,50 @@ +RegisterServerEvent('chatCommandEntered') +RegisterServerEvent('chatMessageEntered') + +AddEventHandler('chatMessageEntered', function(name, color, message) + if not name or not color or not message or #color ~= 3 then + return + end + + TriggerEvent('chatMessage', source, name, message) + + if not WasEventCanceled() then + TriggerClientEvent('chatMessage', -1, name, color, message) + end + + print(name .. ': ' .. message) +end) + +-- player join messages +AddEventHandler('playerActivated', function() + TriggerClientEvent('chatMessage', -1, '', { 0, 0, 0 }, '^2* ' .. GetPlayerName(source) .. ' joined.') +end) + +AddEventHandler('playerDropped', function(reason) + TriggerClientEvent('chatMessage', -1, '', { 0, 0, 0 }, '^2* ' .. GetPlayerName(source) ..' left (' .. reason .. ')') +end) + +-- say command handler +AddEventHandler('rconCommand', function(commandName, args) + if commandName == "say" then + local msg = table.concat(args, ' ') + + TriggerClientEvent('chatMessage', -1, 'console', { 0, 0x99, 255 }, msg) + RconPrint('console: ' .. msg .. "\n") + + CancelEvent() + end +end) + +-- tell command handler +AddEventHandler('rconCommand', function(commandName, args) + if commandName == "tell" then + local target = table.remove(args, 1) + local msg = table.concat(args, ' ') + + TriggerClientEvent('chatMessage', tonumber(target), 'console', { 0, 0x99, 255 }, msg) + RconPrint('console: ' .. msg .. "\n") + + CancelEvent() + end +end) diff --git a/resources/[system]/chat/html/chat.css b/resources/[system]/chat/html/chat.css new file mode 100644 index 0000000..264df3d --- /dev/null +++ b/resources/[system]/chat/html/chat.css @@ -0,0 +1,102 @@ +body +{ + background-color: transparent; + margin: 0px; +} + +ul +{ + margin: 0px; + padding: 0px; + list-style-type: none; /* hii */ +} + +#chat +{ + position: absolute; + top: 30px; + left: 30px; + padding: 7px; + width: 30%; + font-size: 20px; + font-family: "Segoe UI", "Segoe UI Symbol", "Segoe UI Emoji", Arial, sans-serif; + color: #fff; + overflow: hidden; + text-shadow: 0px 0px 1px #333; +} + +input.fake +{ + position: absolute; + top: -10000px; + left: -10000px; +} + +#chatInputHas +{ + display: none; +} + +#chatInputHas strong +{ + display: inline-block; + vertical-align: middle; + text-transform: uppercase; + height: 29px; + line-height: 26px; +} + +#chatInput +{ + background-color: transparent; + border: none; + outline: none !important; + padding: 3px; + + display: inline-block; + vertical-align: middle; + + margin: 0px; + color: #fff; + text-shadow: 0px 0px 1px #333; + + font: inherit; + + white-space: pre; + overflow: hidden; +} + +#chatInput > div:first-child +{ + display: inline-block; + vertical-align: middle; + height: 23px; + line-height: 20px; +} + +#chatInput .caret +{ + display: inline-block; + min-width: 1px; + height: 22px; + margin-left: 1px; + vertical-align: bottom; + background-color: #fff; + box-shadow: 0px 0px 1px #333; + color: white; +} + +#chatBuffer +{ + height: 240px; + overflow: hidden; +} + +.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: #cc0000;} \ No newline at end of file diff --git a/resources/[system]/chat/html/chat.html b/resources/[system]/chat/html/chat.html new file mode 100644 index 0000000..c2781e0 --- /dev/null +++ b/resources/[system]/chat/html/chat.html @@ -0,0 +1,20 @@ + + + + + + + + +
+
+
    +
+
+
+ Chat +
+
+
+ + diff --git a/resources/[system]/chat/html/chat.js b/resources/[system]/chat/html/chat.js new file mode 100644 index 0000000..28a4633 --- /dev/null +++ b/resources/[system]/chat/html/chat.js @@ -0,0 +1,166 @@ +function colorize(string) +{ + var newString = ''; + var inSpan = false; + + for (i = 0; i < string.length; i++) + { + if (string[i] == '^') + { + if (string[i + 1] == '7' || string[i + 1] == '0') + { + if (inSpan) + { + newString += ''; + + inSpan = false; + } + + i += 2; + } + else if (string[i + 1] >= '0' && string[i + 1] <= '9') + { + if (inSpan) + { + newString += ''; + } + + i += 2; + newString += ''; + + inSpan = true; + } + } + + newString += string[i]; + } + + if (inSpan) + { + newString += ''; + } + + return newString; +} + +$(function() +{ + var chatHideTimeout; + var inputShown = false; + + function startHideChat() + { + if (chatHideTimeout) + { + clearTimeout(chatHideTimeout); + } + + if (inputShown) + { + return; + } + + chatHideTimeout = setTimeout(function() + { + if (inputShown) + { + return; + } + + $('#chat').animate({ opacity: 0 }, 300); + }, 7000); + } + + handleResult = function(elem, wasEnter) + { + inputShown = false; + + $('#chatInputHas').hide(); + + startHideChat(); + + var obj = {}; + + if (wasEnter) + { + obj = { message: $(elem).val() }; + } + + $(elem).val(''); + + $.post('http://chat/chatResult', JSON.stringify(obj), function(data) + { + console.log(data); + }); + }; + + $('#chatInput').fakeTextbox(); // // + + $('#chatInput')[0].onPress(function(e) + { + if (e.which == 13) + { + handleResult(this, true); + } + }); + + $(document).keyup(function(e) + { + if (e.keyCode == 27) + { + handleResult($('#chatInput')[0].getTextBox(), false); + } + }); + + $(document).keypress(function(e) + { + if (e.keyCode == 9) + { + e.preventDefault(); + return false; + } + }); + + window.addEventListener('message', function(event) + { + var item = event.data; + + if (item.meta && item.meta == 'openChatBox') + { + inputShown = true; + + $('#chat').css('opacity', '1'); + + $('#chatInputHas').show(); + $('#chatInput')[0].doFocus(); + + return; + } + + // TODO: use some templating stuff for this + var colorR = parseInt(item.color[0]); + var colorG = parseInt(item.color[1]); + var colorB = parseInt(item.color[2]); + + var name = item.name.replace('<', '<'); + var message = item.message.replace('<', '<'); + + message = colorize(message); + + var buf = $('#chatBuffer'); + + var nameStr = ''; + + if (name != '') + { + nameStr = '' + name + ': '; + } + + buf.find('ul').append('
  • ' + nameStr + message + '
  • '); + buf.scrollTop(buf[0].scrollHeight - buf.height()); + + $('#chat').css('opacity', '1'); + + startHideChat(); + }, false); +}); diff --git a/resources/[system]/chat/html/jquery.faketextbox.js b/resources/[system]/chat/html/jquery.faketextbox.js new file mode 100644 index 0000000..a0f4c01 --- /dev/null +++ b/resources/[system]/chat/html/jquery.faketextbox.js @@ -0,0 +1,103 @@ +(function ($) { + $.fn.fakeTextbox = function () { + + return this.each(function () { + + var $me = $(this), + cursorTimer, + $tb = $(''); + + if ($me.data('ftbftw')) { + console.log('already initialized'); + return; + } + + $me.data('ftbftw', 1); + + $tb.insertAfter($me); + + function appendCaret(toHere, position, selStart, selEnd) { + if (position === selStart) { + toHere += "
    "; + } + if (position === selEnd) { + toHere += "
    "; + } + return toHere; + } + + function syncTextbox() { + var tbVal = $tb.val().replace('<', '<'); + var tbLen = tbVal.length; + var selStart = $tb.get(0).selectionStart; + var selEnd = $tb.get(0).selectionEnd; + var newOut = '
    '; + + for (var i = 0; i < tbLen; i++) { + newOut = appendCaret(newOut, i, selStart, selEnd); + newOut += tbVal[i]; + } + + $me.html(colorize(appendCaret(newOut, i, selStart, selEnd) + '
    ')); + if (selStart != selEnd) { + $('.caret', $me).addClass('selection'); + } + } + + $me.click(function () { + $tb.focus(); + }); + + $tb.bind("change keypress keyup", function() + { + setTimeout(syncTextbox, 1); // + }) + .blur(function () { + clearInterval(cursorTimer); + cursorTimer = null; + var $cursor = $('.caret', $me); + $cursor.css({ + visibility: 'visible' + }); + $me.removeClass('focused'); + }).focus(function () { + if (!cursorTimer) { + $me.addClass('focused'); + cursorTimer = window.setInterval(function () { + var $cursor = $('.caret', $me); + if ($cursor.hasClass('selection') || $cursor.css('visibility') === 'hidden') { + $cursor.css({ + visibility: 'visible' + }); + } else { + $cursor.css({ + visibility: 'hidden' + }); + } + }, 500); + } + }); + + this.doFocus = function() + { + $tb.focus(); + }; + + this.onPress = function(f) + { + $tb.bind('keypress', f); + }; + + this.getTextBox = function() + { + return $tb; + }; + + syncTextbox(); + + if ($me.hasClass('initFocus')) { + $tb.focus(); + } + }); + }; +}(jQuery)); diff --git a/resources/[system]/hardcap/__resource.lua b/resources/[system]/hardcap/__resource.lua new file mode 100644 index 0000000..7ad9de5 --- /dev/null +++ b/resources/[system]/hardcap/__resource.lua @@ -0,0 +1,2 @@ +client_script 'client.lua' +server_script 'server.lua' diff --git a/resources/[system]/hardcap/client.lua b/resources/[system]/hardcap/client.lua new file mode 100644 index 0000000..7bcb70d --- /dev/null +++ b/resources/[system]/hardcap/client.lua @@ -0,0 +1,11 @@ +Citizen.CreateThread(function() + while true do + Wait(0) + + if NetworkIsSessionStarted() then + TriggerServerEvent('hardcap:playerActivated') + + return + end + end +end) \ No newline at end of file diff --git a/resources/[system]/hardcap/server.lua b/resources/[system]/hardcap/server.lua new file mode 100644 index 0000000..d5bd0ca --- /dev/null +++ b/resources/[system]/hardcap/server.lua @@ -0,0 +1,29 @@ +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) + print('Connecting: ' .. name) + + if playerCount >= 24 then + print('Full. :(') + + setReason('This server is full (past 24 players).') + CancelEvent() + end +end) diff --git a/resources/[system]/rconlog/__resource.lua b/resources/[system]/rconlog/__resource.lua new file mode 100644 index 0000000..b849ec2 --- /dev/null +++ b/resources/[system]/rconlog/__resource.lua @@ -0,0 +1,2 @@ +client_script 'rconlog_client.lua' +server_script 'rconlog_server.lua' diff --git a/resources/[system]/rconlog/rconlog_client.lua b/resources/[system]/rconlog/rconlog_client.lua new file mode 100644 index 0000000..5361375 --- /dev/null +++ b/resources/[system]/rconlog/rconlog_client.lua @@ -0,0 +1,25 @@ +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) \ No newline at end of file diff --git a/resources/[system]/rconlog/rconlog_server.lua b/resources/[system]/rconlog/rconlog_server.lua new file mode 100644 index 0000000..116aa1d --- /dev/null +++ b/resources/[system]/rconlog/rconlog_server.lua @@ -0,0 +1,80 @@ +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 } + + TriggerClientEvent('rlUpdateNames', GetHostId()) +end) + +RegisterServerEvent('rlUpdateNamesResult') + +AddEventHandler('rlUpdateNamesResult', function(res) + if source ~= 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) + +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) diff --git a/resources/[system]/scoreboard/__resource.lua b/resources/[system]/scoreboard/__resource.lua new file mode 100644 index 0000000..e614e8c --- /dev/null +++ b/resources/[system]/scoreboard/__resource.lua @@ -0,0 +1,18 @@ +description 'Scoreboard' + +-- temporary! +ui_page 'html/scoreboard.html' + +client_script 'scoreboard.lua' + +files { + 'html/scoreboard.html', + 'html/style.css', + 'html/reset.css', + 'html/listener.js', + 'html/res/futurastd-medium.css', + 'html/res/futurastd-medium.eot', + 'html/res/futurastd-medium.woff', + 'html/res/futurastd-medium.ttf', + 'html/res/futurastd-medium.svg', +} \ No newline at end of file diff --git a/resources/[system]/scoreboard/html/listener.js b/resources/[system]/scoreboard/html/listener.js new file mode 100644 index 0000000..19cb4c8 --- /dev/null +++ b/resources/[system]/scoreboard/html/listener.js @@ -0,0 +1,17 @@ +$(function() +{ + window.addEventListener('message', function(event) + { + var item = event.data; + var buf = $('#wrap'); + buf.find('table').append("IDNameWanted level"); + if (item.meta && item.meta == 'close') + { + document.getElementById("ptbl").innerHTML = ""; + $('#wrap').hide(); + return; + } + buf.find('table').append(item.text); + $('#wrap').show(); + }, false); +}); \ No newline at end of file diff --git a/resources/[system]/scoreboard/html/res/futurastd-medium.css b/resources/[system]/scoreboard/html/res/futurastd-medium.css new file mode 100644 index 0000000..4f26d70 --- /dev/null +++ b/resources/[system]/scoreboard/html/res/futurastd-medium.css @@ -0,0 +1,8 @@ +@font-face { + font-family: 'FuturaStdMedium'; + src: url('futurastd-medium.eot'); + src: url('futurastd-medium.eot') format('embedded-opentype'), + url('futurastd-medium.woff') format('woff'), + url('futurastd-medium.ttf') format('truetype'), + url('futurastd-medium.svg#FuturaStdMedium') format('svg'); +} diff --git a/resources/[system]/scoreboard/html/res/futurastd-medium.eot b/resources/[system]/scoreboard/html/res/futurastd-medium.eot new file mode 100644 index 0000000..66de497 Binary files /dev/null and b/resources/[system]/scoreboard/html/res/futurastd-medium.eot differ diff --git a/resources/[system]/scoreboard/html/res/futurastd-medium.svg b/resources/[system]/scoreboard/html/res/futurastd-medium.svg new file mode 100644 index 0000000..e7472be --- /dev/null +++ b/resources/[system]/scoreboard/html/res/futurastd-medium.svg @@ -0,0 +1,913 @@ + + + + +Created by FontForge 20110222 at Mon Aug 4 15:25:24 2014 + By Orthosie Webhosting +Copyright (c) 1987, 1991, 1993, 2002 Adobe Systems Incorporated. All Rights Reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/[system]/scoreboard/html/res/futurastd-medium.ttf b/resources/[system]/scoreboard/html/res/futurastd-medium.ttf new file mode 100644 index 0000000..f87af22 Binary files /dev/null and b/resources/[system]/scoreboard/html/res/futurastd-medium.ttf differ diff --git a/resources/[system]/scoreboard/html/res/futurastd-medium.woff b/resources/[system]/scoreboard/html/res/futurastd-medium.woff new file mode 100644 index 0000000..f234cf3 Binary files /dev/null and b/resources/[system]/scoreboard/html/res/futurastd-medium.woff differ diff --git a/resources/[system]/scoreboard/html/reset.css b/resources/[system]/scoreboard/html/reset.css new file mode 100644 index 0000000..af94440 --- /dev/null +++ b/resources/[system]/scoreboard/html/reset.css @@ -0,0 +1,48 @@ +/* http://meyerweb.com/eric/tools/css/reset/ + v2.0 | 20110126 + License: none (public domain) +*/ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} +body { + line-height: 1; +} +ol, ul { + list-style: none; +} +blockquote, q { + quotes: none; +} +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} \ No newline at end of file diff --git a/resources/[system]/scoreboard/html/scoreboard.html b/resources/[system]/scoreboard/html/scoreboard.html new file mode 100644 index 0000000..f4df0a1 --- /dev/null +++ b/resources/[system]/scoreboard/html/scoreboard.html @@ -0,0 +1,14 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/resources/[system]/scoreboard/html/style.css b/resources/[system]/scoreboard/html/style.css new file mode 100644 index 0000000..5da5420 --- /dev/null +++ b/resources/[system]/scoreboard/html/style.css @@ -0,0 +1,40 @@ +@import url('res/futurastd-medium.css'); + +html { + color: #fff; +} + +#wrap { + width: 500px; + min-height: 185px; + margin-top: 10%; + margin-left: auto; + margin-right: auto; + background-color: #fff; + box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.16), 0px 2px 10px 0px rgba(0, 0, 0, 0.12); + color: rgba(255, 255, 255, 0.9); +} + +table { + text-align: left; +} + +th, td { + padding-left: 25px; +} + +th { + padding-top: 10px; + height: 40px; +} + +tr { + font-size: 120%; + font-family: 'Segoe UI'; + /*text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.9);*/ +} + +tr.heading { + background-color: #43A047; + color: rgba(255, 255, 255, 0.9); +} \ No newline at end of file diff --git a/resources/[system]/scoreboard/scoreboard.lua b/resources/[system]/scoreboard/scoreboard.lua new file mode 100644 index 0000000..88685d7 --- /dev/null +++ b/resources/[system]/scoreboard/scoreboard.lua @@ -0,0 +1,48 @@ +local listOn = false + +Citizen.CreateThread(function() + listOn = false + while true do + Wait(0) + + if IsControlPressed(0, 27)--[[ INPUT_PHONE ]] then + if not listOn then + local players = {} + ptable = GetPlayers() + for _, i in ipairs(ptable) do + local wantedLevel = GetPlayerWantedLevel(i) + r, g, b = GetPlayerRgbColour(i) + table.insert(players, + '' .. GetPlayerServerId(i) .. '' .. GetPlayerName(i) .. '' .. (wantedLevel and wantedLevel or tostring(0)) .. '' + ) + end + + SendNUIMessage({ text = table.concat(players) }) + + listOn = true + while listOn do + Wait(0) + if(IsControlPressed(0, 27) == false) then + listOn = false + SendNUIMessage({ + meta = 'close' + }) + break + end + end + end + end + end +end) + +function GetPlayers() + local players = {} + + for i = 0, 31 do + if NetworkIsPlayerActive(i) then + table.insert(players, i) + end + end + + return players +end \ No newline at end of file diff --git a/resources/[system]/sessionmanager/__resource.lua b/resources/[system]/sessionmanager/__resource.lua new file mode 100644 index 0000000..70dc952 --- /dev/null +++ b/resources/[system]/sessionmanager/__resource.lua @@ -0,0 +1,11 @@ +client_scripts { + 'client/initial.lua', + 'client/leavehandler.lua', + 'client/activationhandler.lua', + 'client/hostservice.lua', + 'client/sessionstarter.lua' +} + +server_script 'server/host_lock.lua' + +export 'serviceHostStuff' diff --git a/resources/[system]/sessionmanager/client/activationhandler.lua b/resources/[system]/sessionmanager/client/activationhandler.lua new file mode 100644 index 0000000..43ef662 --- /dev/null +++ b/resources/[system]/sessionmanager/client/activationhandler.lua @@ -0,0 +1,27 @@ +-- triggers an event when the local network player becomes active +AddEventHandler('sessionInitialized', function() + local playerId = GetPlayerId() + + -- create a looping thread + CreateThread(function() + -- wait until the player becomes active + while not IsNetworkPlayerActive(playerId) do + Wait(0) + end + + -- set some defaults + AllowGameToPauseForStreaming(true) + + SetMaxWantedLevel(6) + SetWantedMultiplier(0.9999999) + SetCreateRandomCops(true) + SetDitchPoliceModels(false) + + DisplayPlayerNames(true) + NetworkSetHealthReticuleOption(true) + + -- trigger an event on both the local client and the server + TriggerEvent('playerActivated') + TriggerServerEvent('playerActivated') + end) +end) diff --git a/resources/[system]/sessionmanager/client/hostservice.lua b/resources/[system]/sessionmanager/client/hostservice.lua new file mode 100644 index 0000000..b731ad4 --- /dev/null +++ b/resources/[system]/sessionmanager/client/hostservice.lua @@ -0,0 +1,55 @@ +-- serving the duties of the office of the host + +-- two functions from GTA script; they do 'something lock-ish' +local function acquireHostLock() + if IsThisMachineTheServer() then + SetThisMachineRunningServerScript(true) + return true + end + + return false +end + +local function releaseHostLock() + SetThisMachineRunningServerScript(false) +end + +-- handle msgGetReadyToStartPlaying sending +function serviceHostStuff() + -- acquire the host lock + if acquireHostLock() then + -- check if players want to join + for i = 0, 31 do + -- does this index? + if PlayerWantsToJoinNetworkGame(i) then + -- well, get ready to start playing! + TellNetPlayerToStartPlaying(i, 0) + + TriggerServerEvent('playerJoining', i) + end + end + + -- release the host lock + releaseHostLock() + end +end + +-- host service loop +CreateThread(function() + NetworkSetScriptLobbyState(false) + SwitchArrowAboveBlippedPickups(true) + UsePlayerColourInsteadOfTeamColour(true) + LoadAllPathNodes(true) + SetSyncWeatherAndGameTime(true) + + while true do + Wait(0) + + serviceHostStuff() + + -- launch the local player, for the initial host scenario + if LocalPlayerIsReadyToStartPlaying() then + LaunchLocalPlayerInNetworkGame() + end + end +end) diff --git a/resources/[system]/sessionmanager/client/initial.lua b/resources/[system]/sessionmanager/client/initial.lua new file mode 100644 index 0000000..34674ce --- /dev/null +++ b/resources/[system]/sessionmanager/client/initial.lua @@ -0,0 +1,124 @@ +-- state variable for session host lock +local sessionHostPending = false +local sessionHostResult + +AddEventHandler('sessionHostResult', function(result) + if not sessionHostPending then + return + end + + sessionHostResult = result + sessionHostPending = false +end) + +local attempts = 0 + +-- allow early script to create the player +AddEventHandler('playerInfoCreated', function() + CreateThread(function() + -- so that the game won't trigger the citizen disconnect handler + SafeguardDisconnect(true) + + -- loop for 3 times + while attempts < 3 do + -- 'find' games (this will store the host session in memory, or if no host exists, tell us later) + NetworkFindGame(16, false, 0, 0) + + -- we don't have to wait for the finding to complete; TestSessionFind.cpp in hooks_ny will instantly return + local gamesFound = NetworkGetNumberOfGames(_r) + + -- if we found at least one game (if the game isn't hooked, this can be any amount; but + -- we can't trust the implementation in that case anyway) + local needsToHost = true -- whether we need to host after completing a possible join + + if gamesFound > 0 then + -- join the game + NetworkJoinGame(0) + + SetLoadingText('Entering session') -- status text + + -- wait for the join to complete + while NetworkJoinGamePending() do + Wait(0) + end + + -- if we succeeded, we're now a session member, and will not need to host + if NetworkJoinGameSucceeded() then + needsToHost = false + break + end + end + + -- if we didn't find any games, or a join timed out, we'll *consider* hosting + if needsToHost then + -- make sure we don't have an actual other host waiting for us + sessionHostPending = true -- to trigger a wait loop below + + TriggerServerEvent('hostingSession') + + SetLoadingText('Initializing session') -- some vague status text + + -- wait for the server to respond to our request + while sessionHostPending do + Wait(0) + end + + if sessionHostResult == 'wait' then + -- TODO: not implemented yet: wait for a message from the server, then attempt finding a game/joining a game again + sessionHostPending = true + + while sessionHostPending do + Wait(0) + end + + if sessionHostResult == 'free' then + goto endLoop + end + end + + if sessionHostResult == 'conflict' and gamesFound > 0 then + -- there's already a host which is working perfectly fine; show a message to the player + --error('session creation conflict: could not connect to original host') + echo("session creation conflict\n") + goto endLoop + end + + -- we got a green light to host; start hosting + if not NetworkHostGameE1(16, false, 32, false, 0, 0) then + echo("session creation failure from NetworkHostGameE1\n") + error('failed to initialize session') + end + + -- wait for internal processing to complete + while NetworkHostGamePending() do + Wait(0) + end + + -- another failure check + if not NetworkHostGameSucceeded() then + echo("session creation failure from NetworkHostGameSucceeded\n") + error('failed to initialize session') + end + + TriggerServerEvent('hostedSession') + + break + end + + ::endLoop:: + attempts = attempts + 1 + end + + SafeguardDisconnect(false) + + if attempts >= 3 then + error("Could not connect to session provider.") + end + + SetLoadingText('Look at that!') + + -- signal local game-specific resources to start + TriggerEvent('sessionInitialized') + TriggerServerEvent('sessionInitialized') + end) +end) diff --git a/resources/[system]/sessionmanager/client/leavehandler.lua b/resources/[system]/sessionmanager/client/leavehandler.lua new file mode 100644 index 0000000..c6aca3c --- /dev/null +++ b/resources/[system]/sessionmanager/client/leavehandler.lua @@ -0,0 +1,31 @@ +-- handles the script end of the flag the 'leave game' option in the pause menu sets +CreateThread(function() + while true do + Wait(0) + + -- if the flag is set + if DoesGameCodeWantToLeaveNetworkSession() then + -- if we're part of a started session; end it first (FIXME: will this break others when we're host?) + if NetworkIsSessionStarted() then + NetworkEndSession() + + -- wait for the session to be ended + while NetworkEndSessionPending() do + Wait(0) + end + end + + -- attempt to leave the game + NetworkLeaveGame() + + -- while we're waiting to leave... + while NetworkLeaveGamePending() do + Wait(0) + end + + -- reinitialize the game as a network game (TODO: call into citigame for UI/NetLibrary leaving) + --ShutdownAndLaunchNetworkGame(0) -- episode id is arg + ShutdownNetworkCit('Left'); + end + end +end) diff --git a/resources/[system]/sessionmanager/client/sessionstarter.lua b/resources/[system]/sessionmanager/client/sessionstarter.lua new file mode 100644 index 0000000..a5233d8 --- /dev/null +++ b/resources/[system]/sessionmanager/client/sessionstarter.lua @@ -0,0 +1,26 @@ +-- more rline stuff, this does some late-term session management (which I think has some race condition with launching the network player?) +AddEventHandler('sessionInitialized', function() + if IsThisMachineTheServer() then + -- unknown stuff, seems needed though + NetworkChangeExtendedGameConfigCit() + + CreateThread(function() + Wait(1500) + + if not NetworkIsSessionStarted() then + NetworkStartSession() + + while NetworkStartSessionPending() do + Wait(0) + end + + if not NetworkStartSessionSucceeded() then + ForceLoadingScreen(0) + SetMsgForLoadingScreen("MO_SNI") + + return + end + end + end) + end +end) diff --git a/resources/[system]/sessionmanager/server/host_lock.lua b/resources/[system]/sessionmanager/server/host_lock.lua new file mode 100644 index 0000000..ee25f4e --- /dev/null +++ b/resources/[system]/sessionmanager/server/host_lock.lua @@ -0,0 +1,67 @@ +-- 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() >= 1 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) diff --git a/resources/[system]/spawnmanager/__resource.lua b/resources/[system]/spawnmanager/__resource.lua new file mode 100644 index 0000000..e77aef6 --- /dev/null +++ b/resources/[system]/spawnmanager/__resource.lua @@ -0,0 +1,9 @@ +client_script 'spawnmanager.lua' + +export 'getRandomSpawnPoint' +export 'spawnPlayer' +export 'addSpawnPoint' +export 'loadSpawns' +export 'setAutoSpawn' +export 'setAutoSpawnCallback' +export 'forceRespawn' diff --git a/resources/[system]/spawnmanager/spawnmanager.lua b/resources/[system]/spawnmanager/spawnmanager.lua new file mode 100644 index 0000000..3b5a934 --- /dev/null +++ b/resources/[system]/spawnmanager/spawnmanager.lua @@ -0,0 +1,350 @@ +-- in-memory spawnpoint array for this script execution instance +local spawnPoints = {} + +-- auto-spawn enabled flag +local autoSpawnEnabled = false +local autoSpawnCallback + +-- support for mapmanager maps +AddEventHandler('getMapDirectives', function(add) + -- call the remote callback + add('spawnpoint', function(state, model) + -- return another callback to pass coordinates and so on (as such syntax would be [spawnpoint 'model' { options/coords }]) + return function(opts) + local x, y, z, heading + + local s, e = pcall(function() + -- is this a map or an array? + if opts.x then + x = opts.x + y = opts.y + z = opts.z + else + x = opts[1] + y = opts[2] + z = opts[3] + end + + x = x + 0.0001 + y = y + 0.0001 + z = z + 0.0001 + + -- get a heading and force it to a float, or just default to null + heading = opts.heading and (opts.heading + 0.01) or 0 + + -- add the spawnpoint + addSpawnPoint({ + x = x, y = y, z = z, + heading = heading, + model = model + }) + + -- recalculate the model for storage + if not tonumber(model) then + model = GetHashKey(model, _r) + end + + -- store the spawn data in the state so we can erase it later on + state.add('xyz', { x, y, z }) + state.add('model', model) + end) + + if not s then + Citizen.Trace(e .. "\n") + end + end + -- delete callback follows on the next line + end, function(state, arg) + -- loop through all spawn points to find one with our state + for i, sp in ipairs(spawnPoints) do + -- if it matches... + if sp.x == state.xyz[1] and sp.y == state.xyz[2] and sp.z == state.xyz[3] and sp.model == state.model then + -- remove it. + table.remove(spawnPoints, i) + return + end + end + end) +end) + + +-- loads a set of spawn points from a JSON string +function loadSpawns(spawnString) + -- decode the JSON string + local data = json.decode(spawnString) + + -- do we have a 'spawns' field? + if not data.spawns then + error("no 'spawns' in JSON data") + end + + -- loop through the spawns + for i, spawn in ipairs(data.spawns) do + -- and add it to the list (validating as we go) + addSpawnPoint(spawn) + end +end + +function addSpawnPoint(spawn) + -- validate the spawn (position) + if not tonumber(spawn.x) or not tonumber(spawn.y) or not tonumber(spawn.z) then + error("invalid spawn position") + end + + -- heading + if not tonumber(spawn.heading) then + error("invalid spawn heading") + end + + -- model (try integer first, if not, hash it) + local model = spawn.model + + if not tonumber(spawn.model) then + model = GetHashKey(spawn.model) + end + + -- is the model actually a model? + if not IsModelInCdimage(model) then + error("invalid spawn model") + end + + -- is is even a ped? + -- not in V? + --[[if not IsThisModelAPed(model) then + error("this model ain't a ped!") + end]] + + -- overwrite the model in case we hashed it + spawn.model = model + + -- all OK, add the spawn entry to the list + table.insert(spawnPoints, spawn) +end + +-- changes the auto-spawn flag +function setAutoSpawn(enabled) + autoSpawnEnabled = enabled +end + +-- sets a callback to execute instead of 'native' spawning when trying to auto-spawn +function setAutoSpawnCallback(cb) + autoSpawnCallback = cb + autoSpawnEnabled = true +end + +-- function as existing in original R* scripts +local function freezePlayer(id, freeze) + local player = id + SetPlayerControl(player, not freeze, false) + + local ped = GetPlayerPed(player) + + if not freeze then + if not IsEntityVisible(ped) then + SetEntityVisible(ped, true) + end + + if not IsPedInAnyVehicle(ped) then + SetEntityCollision(ped, true) + end + + FreezeEntityPosition(ped, false) + --SetCharNeverTargetted(ped, false) + SetPlayerInvincible(player, false) + else + if IsEntityVisible(ped) then + SetEntityVisible(ped, false) + end + + SetEntityCollision(ped, false) + FreezeEntityPosition(ped, true) + --SetCharNeverTargetted(ped, true) + SetPlayerInvincible(player, true) + --RemovePtfxFromPed(ped) + + if not IsPedFatallyInjured(ped) then + ClearPedTasksImmediately(ped) + end + end +end + +function loadScene(x, y, z) + NewLoadSceneStart(x, y, z, 0.0, 0.0, 0.0, 20.0, 0) + + while IsNewLoadSceneActive() do + networkTimer = GetNetworkTimer() + + NetworkUpdateLoadScene() + end +end + +-- to prevent trying to spawn multiple times +local spawnLock = false + +-- spawns the current player at a certain spawn point index (or a random one, for that matter) +function spawnPlayer(spawnIdx, cb) + if spawnLock then + return + end + + spawnLock = true + + Citizen.CreateThread(function() + DoScreenFadeOut(500) + + while IsScreenFadingOut() do + Citizen.Wait(0) + end + + -- if the spawn isn't set, select a random one + if not spawnIdx then + spawnIdx = GetRandomIntInRange(1, #spawnPoints + 1) + end + + -- get the spawn from the array + local spawn + + if type(spawnIdx) == 'table' then + spawn = spawnIdx + else + spawn = spawnPoints[spawnIdx] + end + + -- validate the index + if not spawn then + Citizen.Trace("tried to spawn at an invalid spawn index\n") + + spawnLock = false + + return + end + + -- freeze the local player + freezePlayer(PlayerId(), true) + + -- if the spawn has a model set + if spawn.model then + RequestModel(spawn.model) + + -- load the model for this spawn + while not HasModelLoaded(spawn.model) do + RequestModel(spawn.model) + + Wait(0) + end + + -- change the player model + SetPlayerModel(PlayerId(), spawn.model) + + -- release the player model + SetModelAsNoLongerNeeded(spawn.model) + end + + -- preload collisions for the spawnpoint + RequestCollisionAtCoord(spawn.x, spawn.y, spawn.z) + + -- spawn the player + --ResurrectNetworkPlayer(GetPlayerId(), spawn.x, spawn.y, spawn.z, spawn.heading) + local ped = GetPlayerPed(-1) + + -- V requires setting coords as well + SetEntityCoordsNoOffset(ped, spawn.x, spawn.y, spawn.z, false, false, false, true) + + NetworkResurrectLocalPlayer(spawn.x, spawn.y, spawn.z, spawn.heading, true, true, false) + + -- gamelogic-style cleanup stuff + ClearPedTasksImmediately(ped) + --SetEntityHealth(ped, 300) -- TODO: allow configuration of this? + RemoveAllPedWeapons(ped) -- TODO: make configurable (V behavior?) + ClearPlayerWantedLevel(PlayerId()) + + -- why is this even a flag? + --SetCharWillFlyThroughWindscreen(ped, false) + + -- set primary camera heading + --SetGameCamHeading(spawn.heading) + --CamRestoreJumpcut(GetGameCam()) + + -- load the scene; streaming expects us to do it + --ForceLoadingScreen(true) + --loadScene(spawn.x, spawn.y, spawn.z) + --ForceLoadingScreen(false) + + ShutdownLoadingScreen() + + DoScreenFadeIn(500) + + while IsScreenFadingIn() do + Citizen.Wait(0) + end + + -- and unfreeze the player + freezePlayer(PlayerId(), false) + + TriggerEvent('playerSpawned', spawn) + + if cb then + cb(spawn) + end + + spawnLock = false + end) +end + +-- automatic spawning monitor thread, too +local respawnForced +local diedAt + +Citizen.CreateThread(function() + -- main loop thing + while true do + Citizen.Wait(50) + + local playerPed = GetPlayerPed(-1) + + if playerPed and playerPed ~= -1 then + -- check if we want to autospawn + if autoSpawnEnabled then + if NetworkIsPlayerActive(PlayerId()) then + if (diedAt and (GetTimeDifference(GetGameTimer(), diedAt) > 2000)) or respawnForced then + Citizen.Trace("forcin' respawn\n") + + if autoSpawnCallback then + autoSpawnCallback() + else + spawnPlayer() + end + + respawnForced = false + end + end + end + + if IsEntityDead(playerPed) then + if not diedAt then + diedAt = GetGameTimer() + end + else + diedAt = nil + end + end + end +end) + +function forceRespawn() + spawnLock = false + respawnForced = true +end + +--[[AddEventHandler('playerInfoCreated', function() + loadSpawns(json.encode({ + spawns = { + { x = -238.511, y = 954.025, z = 11.0803, heading = 90.0, model = 'ig_brucie' }, + { x = -310.001, y = 945.603, z = 14.3728, heading = 90.0, model = 'ig_bulgarin' }, + } + })) +end) + +AddEventHandler('playerActivated', function() + respawnForced = true +end)]] diff --git a/resources/[test]/betaguns/__resource.lua b/resources/[test]/betaguns/__resource.lua new file mode 100644 index 0000000..7ad9de5 --- /dev/null +++ b/resources/[test]/betaguns/__resource.lua @@ -0,0 +1,2 @@ +client_script 'client.lua' +server_script 'server.lua' diff --git a/resources/[test]/betaguns/client.lua b/resources/[test]/betaguns/client.lua new file mode 100644 index 0000000..176599f --- /dev/null +++ b/resources/[test]/betaguns/client.lua @@ -0,0 +1,208 @@ +local function getPickupType(i) + if i == 2 then + return 30 + elseif i == 1 then + return 31 + end + + local w = GenerateRandomIntInRange(5, 17, _i) + + if w == 8 or w == 6 then + w = 4 + end + + return w +end + +local function createPickup(ptype, etype, unk, x, y, z) + if ptype == 30 then + CreatePickupWithAmmo(0x972daa10, etype, 0, x, y, z, _i) + elseif ptype == 31 then + CreatePickupWithAmmo(0x3fc62578, etype, 0, x, y, z, _i) + else + CreatePickupWithAmmo(GetWeapontypeModel(ptype, _i), etype, 60, x, y, z, _i) + end +end + +local pickupSeed + +local function createPickups() + echo("creating algo pickups\n") + + SetRandomSeed(pickupSeed) + + createPickup( getPickupType( 2 ), 23, 200, -563.10640000, 293.52680000, 5.65930000 ) + createPickup( getPickupType( 2 ), 23, 200, 79.41570000, -839.53680000, 3.99560000 ) + createPickup( getPickupType( 2 ), 23, 200, -277.35550000, -533.76340000, 3.92420000 ) + createPickup( getPickupType( 2 ), 23, 200, -491.51540000, -173.97790000, 6.90340000 ) + createPickup( getPickupType( 2 ), 23, 200, -235.68930000, 739.30850000, 6.12510000 ) + createPickup( getPickupType( 2 ), 23, 200, -539.49120000, 1362.38800000, 16.47050000 ) + createPickup( getPickupType( 2 ), 23, 200, -180.02360000, -823.41240000, 4.11750000 ) + createPickup( getPickupType( 2 ), 23, 200, 173.60920000, 236.49170000, 13.76010000 ) + createPickup( getPickupType( 2 ), 23, 200, 89.24590000, 1152.34900000, 13.57080000 ) + createPickup( getPickupType( 2 ), 23, 200, 63.60470000, -439.60590000, 13.75830000 ) + createPickup( getPickupType( 2 ), 23, 200, -226.95040000, 1714.70300000, 14.75500000 ) + createPickup( getPickupType( 2 ), 23, 200, 130.44570000, 467.39240000, 13.91780000 ) + createPickup( getPickupType( 2 ), 23, 200, -529.52310000, -339.29980000, 5.04460000 ) + createPickup( getPickupType( 2 ), 23, 200, -477.98870000, 1707.35300000, 7.46380000 ) + createPickup( getPickupType( 2 ), 23, 200, -636.54130000, -45.71210000, 3.81230000 ) + createPickup( getPickupType( 2 ), 23, 200, 140.68720000, -857.79680000, 3.77320000 ) + --createPickup( getPickupType( 2 ), 23, 200, -108.89000000, 64499, 4.11910000 ) + createPickup( getPickupType( 2 ), 23, 200, 348.54010000, -431.52940000, 3.54320000 ) + createPickup( getPickupType( 2 ), 23, 200, 166.63900000, 1080.60900000, 13.62470000 ) + createPickup( getPickupType( 2 ), 23, 200, -145.57280000, 1694.71300000, 15.72350000 ) + createPickup( getPickupType( 2 ), 23, 200, 64.54370000, 261.20720000, 14.53200000 ) + createPickup( getPickupType( 2 ), 23, 200, -507.19360000, 533.97330000, 5.67160000 ) + createPickup( getPickupType( 2 ), 23, 200, -410.23560000, -141.84080000, 11.61790000 ) + createPickup( getPickupType( 2 ), 23, 200, -248.26890000, -589.95000000, 3.78540000 ) + createPickup( getPickupType( 2 ), 23, 200, 115.38710000, 741.87240000, 13.56160000 ) + createPickup( getPickupType( 2 ), 23, 200, 49.21290000, 1350.85200000, 15.25260000 ) + createPickup( getPickupType( 2 ), 23, 200, 332.02520000, -158.35070000, 8.06910000 ) + createPickup( getPickupType( 1 ), 23, 200, -462.60650000, 775.56370000, 8.98430000 ) + createPickup( getPickupType( 1 ), 23, 200, -66.39730000, 1550.17700000, 17.64730000 ) + createPickup( getPickupType( 1 ), 23, 200, -47.94850000, 35.91300000, 13.84780000 ) + createPickup( getPickupType( 1 ), 23, 200, -210.80500000, 1410.40400000, 19.35510000 ) + createPickup( getPickupType( 1 ), 23, 200, 136.81580000, 387.45690000, 14.02680000 ) + createPickup( getPickupType( 1 ), 23, 200, -604.36200000, 339.06450000, 3.67190000 ) + createPickup( getPickupType( 1 ), 23, 200, -135.90700000, 819.94900000, 17.62560000 ) + createPickup( getPickupType( 1 ), 23, 200, -437.64390000, 430.90700000, 8.93740000 ) + createPickup( getPickupType( 1 ), 23, 200, -522.79810000, 1018.30500000, 8.79210000 ) + createPickup( getPickupType( 1 ), 23, 200, -593.54960000, 1165.60900000, 8.94090000 ) + createPickup( getPickupType( 1 ), 23, 200, 89.78390000, 1251.53900000, 14.86610000 ) + createPickup( getPickupType( 1 ), 23, 200, -108.15450000, 1271.20900000, 19.43000000 ) + createPickup( getPickupType( 1 ), 23, 200, -5.26000000, -447.87000000, 13.75820000 ) + createPickup( getPickupType( 1 ), 23, 200, 171.83730000, -807.45750000, 3.97040000 ) + createPickup( getPickupType( 1 ), 23, 200, 0.32430000, -761.24270000, 4.08570000 ) + createPickup( getPickupType( 1 ), 23, 200, -526.37620000, 593.51290000, 12.12300000 ) + createPickup( getPickupType( 1 ), 23, 200, -554.97370000, 806.93090000, 8.05520000 ) + createPickup( getPickupType( 1 ), 23, 200, 13.89740000, 1147.71300000, 13.24760000 ) + createPickup( getPickupType( 1 ), 23, 200, 179.53490000, 691.26530000, 7.18630000 ) + createPickup( getPickupType( 1 ), 23, 200, -463.63800000, 899.77910000, 8.96270000 ) + createPickup( getPickupType( 1 ), 23, 200, -467.32180000, 1556.19000000, 17.47570000 ) + createPickup( getPickupType( 1 ), 23, 200, -284.66330000, 1600.64600000, 19.41570000 ) + createPickup( getPickupType( 1 ), 23, 200, -311.56230000, 1733.49700000, 12.12580000 ) + createPickup( getPickupType( 1 ), 23, 200, -99.43640000, 1350.29900000, 19.41500000 ) + createPickup( getPickupType( 1 ), 23, 200, -534.05160000, 1610.99600000, 8.39809000 ) + createPickup( getPickupType( 1 ), 23, 200, 91.99830000, -318.91000000, 13.61250000 ) + createPickup( getPickupType( 1 ), 23, 200, -619.61000000, -115.38000000, 5.59590000 ) + createPickup( getPickupType( 1 ), 23, 200, 361.06920000, -477.77790000, 4.81800000 ) + createPickup( getPickupType( 1 ), 23, 200, -404.86420000, 1487.26800000, 17.86060000 ) + createPickup( getPickupType( 1 ), 23, 200, -572.86970000, 227.56950000, 3.66220000 ) + createPickup( getPickupType( 0 ), 23, 200, 150.65500000, 913.75690000, 7.35240000 ) + createPickup( getPickupType( 0 ), 23, 200, -151.58120000, 1004.30900000, 5.22660000 ) + createPickup( getPickupType( 0 ), 23, 200, -126.16370000, 554.53360000, 13.76430000 ) + createPickup( getPickupType( 0 ), 23, 200, -389.27630000, 1763.59200000, 8.23320000 ) + createPickup( getPickupType( 0 ), 23, 200, -414.94510000, 376.06220000, 11.07520000 ) + createPickup( getPickupType( 0 ), 23, 200, -348.11940000, 631.42010000, 13.58580000 ) + createPickup( getPickupType( 0 ), 23, 200, -561.26700000, 1457.39500000, 16.53680000 ) + createPickup( getPickupType( 0 ), 23, 200, -656.75510000, 1140.68700000, 8.81430000 ) + createPickup( getPickupType( 0 ), 23, 200, 286.89990000, -392.37890000, 3.97690000 ) + createPickup( getPickupType( 0 ), 23, 200, 267.28000000, -686.88580000, 3.87500000 ) + createPickup( getPickupType( 0 ), 23, 200, 185.85650000, 801.42330000, 7.45320000 ) + createPickup( getPickupType( 0 ), 23, 200, -33.85220000, 772.73390000, 13.64890000 ) + createPickup( getPickupType( 0 ), 23, 200, -658.17000000, 809.31000000, 3.10420000 ) + --createPickup( getPickupType( 0 ), 23, 200, 65123, 1658.10000000, 20.08190000 ) + --createPickup( getPickupType( 0 ), 23, 200, 65307, 1445.20000000, 19.45000000 ) + createPickup( getPickupType( 0 ), 23, 200, -579.01340000, 1414.69400000, 14.47110000 ) + createPickup( getPickupType( 0 ), 23, 200, -570.93210000, 158.32300000, 3.66220000 ) + createPickup( getPickupType( 0 ), 23, 200, -641.65510000, -195.11170000, 3.94450000 ) + createPickup( getPickupType( 0 ), 23, 200, -373.43770000, 1563.55700000, 19.15690000 ) + createPickup( getPickupType( 0 ), 23, 200, -242.26720000, -515.22510000, 3.93780000 ) + createPickup( getPickupType( 0 ), 23, 200, 83.27290000, 128.63830000, 13.74580000 ) + createPickup( getPickupType( 0 ), 23, 200, 100.85700000, -751.07600000, 3.95820000 ) + createPickup( getPickupType( 0 ), 23, 200, 148.27850000, -520.31800000, 13.76100000 ) + createPickup( getPickupType( 0 ), 23, 200, -145.85800000, -436.54300000, 13.71600000 ) + createPickup( getPickupType( 0 ), 23, 200, 30.52840000, -319.98200000, 13.72060000 ) + createPickup( getPickupType( 0 ), 23, 200, -121.35400000, -765.42500000, 4.20210000 ) + createPickup( getPickupType( 0 ), 23, 200, -301.78400000, -408.61900000, 3.82400000 ) + createPickup( getPickupType( 0 ), 23, 200, -221.12500000, -244.63100000, 13.55080000 ) + createPickup( getPickupType( 0 ), 23, 200, 345.52040000, -409.60800000, 3.69260000 ) + createPickup( getPickupType( 0 ), 23, 200, -187.78400000, -104.23300000, 13.59230000 ) + createPickup( getPickupType( 0 ), 23, 200, 23.03970000, -41.08220000, 13.81190000 ) + createPickup( getPickupType( 0 ), 23, 200, -105.90000000, 129.42250000, 13.72260000 ) + createPickup( getPickupType( 0 ), 23, 200, -470.49600000, 190.20460000, 8.85820000 ) + createPickup( getPickupType( 0 ), 23, 200, -108.92700000, 371.07960000, 13.80730000 ) + createPickup( getPickupType( 0 ), 23, 200, -308.23960000, 455.43910000, 13.69960000 ) + createPickup( getPickupType( 0 ), 23, 200, 113.34910000, 650.53870000, 13.71280000 ) + createPickup( getPickupType( 0 ), 23, 200, -69.89160000, 1147.73100000, 13.76710000 ) + createPickup( getPickupType( 0 ), 23, 200, 29.31370000, 761.22520000, 13.50620000 ) + createPickup( getPickupType( 0 ), 23, 200, 52.12710000, 889.81030000, 13.65160000 ) + createPickup( getPickupType( 0 ), 23, 200, -616.57000000, 1001.96400000, 8.91920000 ) + createPickup( getPickupType( 0 ), 23, 200, -491.81600000, 949.22980000, 8.96670000 ) + createPickup( getPickupType( 0 ), 23, 200, 5.79550000, 1028.96500000, 13.72000000 ) + createPickup( getPickupType( 0 ), 23, 200, -542.94400000, 1303.59300000, 16.25890000 ) + createPickup( getPickupType( 0 ), 23, 200, -273.10860000, 1211.38200000, 17.78520000 ) + createPickup( getPickupType( 0 ), 23, 200, -292.14300000, 1331.30300000, 23.60140000 ) + createPickup( getPickupType( 0 ), 23, 200, -364.25800000, 1371.32500000, 14.19140000 ) + createPickup( getPickupType( 0 ), 23, 200, -34.57900000, 1410.33300000, 19.42230000 ) + createPickup( getPickupType( 0 ), 23, 200, -161.42200000, 1555.53300000, 17.37360000 ) + createPickup( getPickupType( 0 ), 23, 200, 210.82320000, -105.36900000, 13.76120000 ) + createPickup( getPickupType( 0 ), 23, 200, -124.28630000, -530.18220000, 13.76020000 ) + createPickup( getPickupType( 0 ), 23, 200, -220.20000000, -883.72000000, 3.67810000 ) + createPickup( getPickupType( 0 ), 23, 200, -107.78000000, -821.86000000, 4.12670000 ) + createPickup( getPickupType( 0 ), 23, 200, 78.03000000, -670.74000000, 13.76770000 ) + createPickup( getPickupType( 0 ), 23, 200, 151.18900000, -613.04700000, 9.63030000 ) + createPickup( getPickupType( 0 ), 23, 200, -27.54000000, -823.69000000, 4.45430000 ) + createPickup( getPickupType( 0 ), 23, 200, 200.28920000, -698.77010000, 3.95350000 ) + createPickup( getPickupType( 0 ), 23, 200, -195.15000000, -711.21000000, 3.96790000 ) + createPickup( getPickupType( 0 ), 23, 200, 100.96000000, -512.62000000, 15.08830000 ) + createPickup( getPickupType( 0 ), 23, 200, 306.47000000, -623.30000000, 4.19430000 ) + createPickup( getPickupType( 0 ), 23, 200, -79.41310000, 614.20590000, 13.76610000 ) + createPickup( getPickupType( 0 ), 23, 200, -385.48000000, 738.49000000, 13.76610000 ) + createPickup( getPickupType( 0 ), 23, 200, -434.99950000, 1101.79400000, 9.24650000 ) + createPickup( getPickupType( 0 ), 23, 200, -31.37680000, 959.19130000, 13.92130000 ) + createPickup( getPickupType( 0 ), 23, 200, -268.25000000, 751.37000000, 10.86610000 ) + createPickup( getPickupType( 0 ), 23, 200, -199.04800000, 880.55260000, 5.15900000 ) + createPickup( getPickupType( 0 ), 23, 200, -330.31000000, 1134.31000000, 12.49350000 ) + createPickup( getPickupType( 0 ), 23, 200, -174.81230000, 938.15850000, 10.64700000 ) + createPickup( getPickupType( 0 ), 23, 200, -115.90590000, 1043.57100000, 5.15920000 ) + createPickup( getPickupType( 0 ), 23, 200, -315.16000000, 867.71000000, 8.89900000 ) + createPickup( getPickupType( 0 ), 23, 200, -564.60000000, 1183.60000000, 9.01900000 ) + createPickup( getPickupType( 0 ), 23, 200, -498.02150000, 1183.31100000, 13.21080000 ) + createPickup( getPickupType( 0 ), 23, 200, -414.29530000, 1365.34600000, 15.55880000 ) + createPickup( getPickupType( 0 ), 23, 200, -468.98060000, 1468.96400000, 17.86100000 ) + createPickup( getPickupType( 0 ), 23, 200, -112.28410000, 1672.74500000, 17.61140000 ) + createPickup( getPickupType( 0 ), 23, 200, -219.91810000, 1277.23200000, 22.09290000 ) + createPickup( getPickupType( 0 ), 23, 200, 2.40000000, 1197.70000000, 16.47760000 ) + createPickup( getPickupType( 0 ), 23, 200, -25.70000000, 1250.90000000, 19.43250000 ) + createPickup( getPickupType( 0 ), 23, 200, -65.74770000, 1498.05800000, 17.44880000 ) + createPickup( getPickupType( 0 ), 23, 200, -383.30600000, 319.06300000, 13.75090000 ) + --createPickup( getPickupType( 0 ), 23, 200, 65250, 344.20000000, 13.66590000 ) + createPickup( getPickupType( 0 ), 23, 200, -212.60000000, 346.70000000, 14.03540000 ) + createPickup( getPickupType( 0 ), 23, 200, -66.26470000, 278.22370000, 13.76360000 ) + createPickup( getPickupType( 0 ), 23, 200, -181.14000000, 491.28420000, 13.71490000 ) + createPickup( getPickupType( 0 ), 23, 200, -24.70000000, 405.20000000, 14.76350000 ) + createPickup( getPickupType( 0 ), 23, 200, 51.61110000, 464.46720000, 13.69600000 ) + createPickup( getPickupType( 0 ), 23, 200, 27.60000000, 374.20000000, 13.70190000 ) + createPickup( getPickupType( 0 ), 23, 200, -603.98900000, 612.11540000, 3.85550000 ) + createPickup( getPickupType( 0 ), 23, 200, -337.70000000, 215.40000000, 13.74920000 ) + createPickup( getPickupType( 0 ), 23, 200, -383.50000000, 556.30000000, 13.77870000 ) + createPickup( getPickupType( 0 ), 23, 200, -442.96920000, 590.37180000, 10.25190000 ) + createPickup( getPickupType( 0 ), 23, 200, 141.80000000, 211.20000000, 13.76310000 ) + createPickup( getPickupType( 0 ), 23, 200, -192.30000000, 162.40000000, 13.98940000 ) + createPickup( getPickupType( 0 ), 23, 200, -348.60300000, -188.71300000, 13.64900000 ) + createPickup( getPickupType( 0 ), 23, 200, -273.48200000, -157.81400000, 13.88300000 ) + createPickup( getPickupType( 0 ), 23, 200, -117.97000000, -335.54000000, 13.73490000 ) + createPickup( getPickupType( 0 ), 23, 200, -12.45000000, -218.40000000, 13.63990000 ) + createPickup( getPickupType( 0 ), 23, 200, 179.94720000, -254.52090000, 11.85560000 ) + createPickup( getPickupType( 0 ), 23, 200, 264.98180000, -302.83180000, 5.59270000 ) + createPickup( getPickupType( 0 ), 23, 200, 162.58500000, -158.31150000, 13.92630000 ) + createPickup( getPickupType( 0 ), 23, 200, 113.02140000, -39.66420000, 13.76250000 ) + createPickup( getPickupType( 0 ), 23, 200, -126.60700000, -117.37200000, 13.81500000 ) + createPickup( getPickupType( 0 ), 23, 200, 207.01740000, 20.70740000, 13.71320000 ) + createPickup( getPickupType( 0 ), 23, 200, -254.45000000, -43.88000000, 13.76330000 ) + createPickup( getPickupType( 0 ), 23, 200, -347.84500000, 105.27390000, 13.81310000 ) + createPickup( getPickupType( 0 ), 23, 200, -345.03400000, -100.46700000, 13.70210000 ) + createPickup( getPickupType( 0 ), 23, 200, -445.05100000, 131.98950000, 8.83120000 ) + createPickup( getPickupType( 0 ), 23, 200, -490.37520000, 25.33320000, 6.86600000 ) + createPickup( getPickupType( 0 ), 23, 200, -572.51200000, 86.31020000, 3.81230000 ) + createPickup( getPickupType( 0 ), 23, 200, 29.85000000, -601.28000000, 13.69580000 ) + createPickup( getPickupType( 0 ), 23, 200, -184.29000000, 102.09000000, 13.76770000 ) +end + +AddEventHandler('createGunPickups', function(seed) + pickupSeed = seed + + RemoveAllPickupsOfType(23) + createPickups() +end) diff --git a/resources/[test]/betaguns/server.lua b/resources/[test]/betaguns/server.lua new file mode 100644 index 0000000..ae82fc6 --- /dev/null +++ b/resources/[test]/betaguns/server.lua @@ -0,0 +1,9 @@ +math.randomseed(GetInstanceId()) + +local randomBase = math.random() + +RegisterServerEvent('playerActivated') + +AddEventHandler('playerActivated', function() + TriggerClientEvent('createGunPickups', source, randomBase) +end) diff --git a/resources/[test]/gameInit/__resource.lua b/resources/[test]/gameInit/__resource.lua new file mode 100644 index 0000000..0fe7326 --- /dev/null +++ b/resources/[test]/gameInit/__resource.lua @@ -0,0 +1,4 @@ +description 'early init for game script' + +client_script 'init.lua' +server_script 'server.lua' diff --git a/resources/[test]/gameInit/init.lua b/resources/[test]/gameInit/init.lua new file mode 100644 index 0000000..6ce844e --- /dev/null +++ b/resources/[test]/gameInit/init.lua @@ -0,0 +1,42 @@ +CreateThread(function() + local bit = function() + return math.random() + end + + local function freezePlayer(id, freeze) + local player = ConvertIntToPlayerindex(id) + SetPlayerControlForNetwork(player, not freeze, false) + + local ped = GetPlayerChar(player, _i) + + if not freeze then + if not IsCharVisible(ped) then + SetCharVisible(ped, true) + end + + if not IsCharInAnyCar(ped) then + SetCharCollision(ped, true) + end + + FreezeCharPosition(ped, false) + SetCharNeverTargetted(ped, false) + SetPlayerInvincible(player, false) + else + FreezeCharPosition(ped, true) + SetCharNeverTargetted(ped, true) + SetPlayerInvincible(player, true) + + if not IsCharFatallyInjured(ped) then + --ClearCharTasksImmediately(ped) + end + end + end + + local player = CreatePlayer(0, -2000.5 + bit(), -2000.5 + bit(), 240.5 + bit(), _i) + + freezePlayer(GetPlayerId(), true) + + SetLoadingText("this is too lovely") + + TriggerEvent('playerInfoCreated') +end) diff --git a/resources/[test]/gameInit/server.lua b/resources/[test]/gameInit/server.lua new file mode 100644 index 0000000..24d8b51 --- /dev/null +++ b/resources/[test]/gameInit/server.lua @@ -0,0 +1,4 @@ +-- prevent stopping gameInit on the server +AddEventHandler('onResourceStop', function(name) + if name == 'gameInit' then CancelEvent() end +end) diff --git a/resources/[test]/keks/__resource.lua b/resources/[test]/keks/__resource.lua new file mode 100644 index 0000000..759f67b --- /dev/null +++ b/resources/[test]/keks/__resource.lua @@ -0,0 +1,6 @@ +files { + 'index.html', + 'keks.css', + 'bankgothic.ttf', + 'loadscreen.jpg' +} diff --git a/resources/[test]/keks/bankgothic.ttf b/resources/[test]/keks/bankgothic.ttf new file mode 100644 index 0000000..f3d8049 Binary files /dev/null and b/resources/[test]/keks/bankgothic.ttf differ diff --git a/resources/[test]/keks/index.html b/resources/[test]/keks/index.html new file mode 100644 index 0000000..1a667ac --- /dev/null +++ b/resources/[test]/keks/index.html @@ -0,0 +1,41 @@ + + + + + +
    +
    +

    Free Mode

    +

    Algonquin

    +
    + +
    +

    Intel

    +
    +

    The Statue of Happiness has a heart. Have one too!

    +
    +
    +
    +
    +
    +
    + + + + diff --git a/resources/[test]/keks/keks.css b/resources/[test]/keks/keks.css new file mode 100644 index 0000000..286e27e --- /dev/null +++ b/resources/[test]/keks/keks.css @@ -0,0 +1,147 @@ +body +{ + margin: 0px; + padding: 0px; +} + +.backdrop +{ + position: relative; + top: 0px; + left: 0px; + width: 100%; + height: 100%; + + background-image: url(loadscreen.jpg); + background-size: 100% 100%; +} + +.bottom +{ + position: absolute; + bottom: 0px; + width: 100%; + height: 100%; +} + +#gradient +{ + position: absolute; + bottom: 0px; + width: 100%; + + height: 25%; + + background: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 1) 100%); +} + +@font-face { + font-family: 'BankGothic'; + src: url('bankgothic.ttf') format('truetype'); + font-weight: normal; + font-style: normal; +} + +h1, h2 { + position: relative; + background: transparent; + z-index: 0; +} +/* add a single stroke */ +h1:before, h2:before { + content: attr(title); + position: absolute; + -webkit-text-stroke: 0.1em #000; + left: 0; + z-index: -1; +} + + +.letni +{ + position: absolute; + left: 5%; + right: 5%; + bottom: 10%; + + z-index: 5; + + color: #fff; + + font-family: "Segoe UI"; +} + +.letni p +{ + font-size: 22px; + + margin-left: 3px; + + margin-top: 0px; +} + +.letni h2 +{ + font-family: BankGothic; + + text-transform: uppercase; + + font-size: 50px; + + margin: 0px; +} + +.top +{ + color: #fff; + + position: absolute; + top: 7%; + left: 5%; + right: 5%; +} + +.top h1 +{ + font-family: BankGothic; + font-size: 60px; + + margin: 0px; +} + +.top h2 +{ + font-family: BankGothic; + font-size: 40px; + + margin: 0px; + + color: #ddd; +} + +.loadbar +{ + width: 100%; + background-color: rgba(140, 140, 140, .9); + height: 20px; + + margin-left: 2px; + margin-right: 3px; + + margin-top: 5px; + margin-bottom: 5px; + + overflow: hidden; + + position: relative; +} + +.thingy +{ + width: 10%; + background-color: #eee; + height: 20px; + + position: absolute; + left: 10%; +} \ No newline at end of file diff --git a/resources/[test]/keks/loadscreen.jpg b/resources/[test]/keks/loadscreen.jpg new file mode 100644 index 0000000..a87caee Binary files /dev/null and b/resources/[test]/keks/loadscreen.jpg differ diff --git a/resources/fivem-awesome1501/__resource.lua b/resources/fivem-awesome1501/__resource.lua new file mode 100644 index 0000000..ec5e35d --- /dev/null +++ b/resources/fivem-awesome1501/__resource.lua @@ -0,0 +1,2 @@ +client_script 'omg.lua' +server_script 'srv.lua' \ No newline at end of file diff --git a/resources/fivem-awesome1501/omg.lua b/resources/fivem-awesome1501/omg.lua new file mode 100644 index 0000000..c3e5ee7 --- /dev/null +++ b/resources/fivem-awesome1501/omg.lua @@ -0,0 +1,78 @@ +Citizen.Trace("OMG FINALLY FIVEM SCRIPTING FROM SERVER-SIDE STUFF WOWOWOWOWOW-zers\n") + +Citizen.CreateThread(function() + while true do + Citizen.Wait(0) + + local playerPed = GetPlayerPed(-1) + + if playerPed and playerPed ~= -1 then + --local pos = GetEntityCoords(playerPed) + + local is, pos = GetPedLastWeaponImpactCoord(playerPed) + + if is then + SetNotificationTextEntry('STRING') + AddTextComponentString(tostring(pos)) + DrawNotification(false, false) + end + end + end +end) + +Citizen.CreateThread(function() + while true do + Citizen.Wait(50) + + local playerPed = GetPlayerPed(-1) + + if playerPed and playerPed ~= -1 then + if IsControlPressed(2, 18) then + SetEntityHeading(playerPed, GetEntityHeading(playerPed) + 15.0) + end + end + end +end) + +Citizen.CreateThread(function() + while true do + Citizen.Wait(250) + + local playerPed = GetPlayerPed(-1) + + if playerPed and playerPed ~= -1 then + if IsControlPressed(0, 11) then + RequestModel(0x2B6DC64A) + + while not HasModelLoaded(0x2B6DC64A) do + Citizen.Wait(0) + end + + local playerCoords = GetEntityCoords(playerPed) + playerCoords = playerCoords + vector3(0, 2, 0) + + local car = CreateVehicle(0x2B6DC64A, playerCoords, 0.0, true, false) + + SetNotificationTextEntry('STRING') + AddTextComponentString('car: ' .. tostring(car)) + DrawNotification(false, false) + + TriggerEvent('isogram', 'a', function(b) + Citizen.Trace('in isogram ' .. tostring(b.a) .. "\n") + + return b.a + 2 + end) + end + end + end +end) + +AddEventHandler('isogram', function(i, s) + Citizen.Trace('in isogram_0 ' .. tostring(i) .. "\n") + + Citizen.Trace('out of isogram_0 ' .. tostring(s({ a = 50 })) .. "\n") +end) + +AddEventHandler('onPlayerJoining', function(netId, name) + TriggerServerEvent('yepThatsMe', netId, name, { a = 'b' }) +end) \ No newline at end of file diff --git a/resources/fivem-awesome1501/srv.lua b/resources/fivem-awesome1501/srv.lua new file mode 100644 index 0000000..e9ac503 --- /dev/null +++ b/resources/fivem-awesome1501/srv.lua @@ -0,0 +1,5 @@ +RegisterServerEvent('yepThatsMe') + +AddEventHandler('yepThatsMe', function(id, name, tab) + print('ytm ', id, name, tab.a) +end) \ No newline at end of file diff --git a/resources/fivem-map-hipster/__resource.lua b/resources/fivem-map-hipster/__resource.lua new file mode 100644 index 0000000..1064a5a --- /dev/null +++ b/resources/fivem-map-hipster/__resource.lua @@ -0,0 +1,3 @@ +resource_type 'map' { gameTypes = { fivem = true } } + +map 'map.lua' diff --git a/resources/fivem-map-hipster/map.lua b/resources/fivem-map-hipster/map.lua new file mode 100644 index 0000000..4fa65e9 --- /dev/null +++ b/resources/fivem-map-hipster/map.lua @@ -0,0 +1,64 @@ +vehicle_generator "airtug" { -54.26639938354492, -1679.548828125, 28.4414, heading = 228.2736053466797 } + +spawnpoint 'a_m_y_hipster_01' { x = -802.311, y = 175.056, z = 72.8446 } +spawnpoint 'a_m_y_hipster_02' { x = -9.96562, y = -1438.54, z = 31.1015 } +spawnpoint 'a_m_y_hipster_01' { x = 0.916756, y = 528.485, z = 174.628 } +spawnpoint 'a_m_y_hipster_02' { x = 1975.86, y = 3821.03, z = 33.4501 } +spawnpoint 'a_m_y_hipster_01' { x = -181.615, y = 852.8, z = 232.701 } +spawnpoint 'a_m_y_hipster_02' { x = 657.723, y = 457.342, z = 144.641 } +spawnpoint 'a_m_y_hipster_01' { x = 134.387, y = 1150.31, z = 231.594 } +spawnpoint 'a_m_y_hipster_02' { x = 726.14, y = 1196.91, z = 326.262 } +spawnpoint 'a_m_y_hipster_01' { x = 740.792, y = 1283.62, z = 360.297 } +spawnpoint 'a_m_y_hipster_02' { x = -437.009, y = 1059.59, z = 327.331 } +spawnpoint 'a_m_y_hipster_01' { x = -428.771, y = 1596.8, z = 356.338 } +spawnpoint 'a_m_y_hipster_02' { x = -1348.78, y = 723.87, z = 186.45 } +spawnpoint 'a_m_y_hipster_01' { x = -1543.24, y = 830.069, z = 182.132 } +spawnpoint 'a_m_y_hipster_02' { x = -2150.48, y = 222.019, z = 184.602 } +spawnpoint 'a_m_y_hipster_01' { x = -3032.13, y = 22.2157, z = 10.1184 } +spawnpoint 'a_m_y_hipster_02' { x = 3063.97, y = 5608.88, z = 209.245 } +spawnpoint 'a_m_y_hipster_01' { x = -2614.35, y = 1872.49, z = 167.32 } +spawnpoint 'a_m_y_hipster_02' { x = -1873.94, y = 2088.73, z = 140.994 } +spawnpoint 'a_m_y_hipster_01' { x = -597.177, y = 2092.16, z = 131.413 } +spawnpoint 'a_m_y_hipster_02' { x = 967.126, y = 2226.99, z = 54.0588 } +spawnpoint 'a_m_y_hipster_01' { x = -338.043, y = 2829, z = 56.0871 } +spawnpoint 'a_m_y_hipster_02' { x = 1082.25, y = -696.921, z = 58.0099 } +spawnpoint 'a_m_y_hipster_01' { x = 1658.31, y = -13.9234, z = 169.992 } +spawnpoint 'a_m_y_hipster_02' { x = 2522.98, y = -384.436, z = 92.9928 } +spawnpoint 'a_m_y_hipster_01' { x = 2826.27, y = -656.489, z = 1.87841 } +spawnpoint 'a_m_y_hipster_02' { x = 2851.12, y = 1467.5, z = 24.5554 } +spawnpoint 'a_m_y_hipster_01' { x = 2336.33, y = 2535.39, z = 46.5177 } +spawnpoint 'a_m_y_hipster_02' { x = 2410.46, y = 3077.88, z = 48.1529 } +spawnpoint 'a_m_y_hipster_01' { x = 2451.15, y = 3768.37, z = 41.3477 } +spawnpoint 'a_m_y_hipster_02' { x = 3337.78, y = 5174.8, z = 18.2108 } +spawnpoint 'a_m_y_hipster_01' { x = -1119.33, y = 4978.52, z = 186.26 } +spawnpoint 'a_m_y_hipster_02' { x = 2877.3, y = 5911.57, z = 369.618 } +spawnpoint 'a_m_y_hipster_01' { x = 2942.1, y = 5306.73, z = 101.52 } +spawnpoint 'a_m_y_hipster_02' { x = 2211.29, y = 5577.94, z = 53.872 } +spawnpoint 'a_m_y_hipster_01' { x = 1602.39, y = 6623.02, z = 15.8417 } +spawnpoint 'a_m_y_hipster_02' { x = 66.0113, y = 7203.58, z = 3.16 } +spawnpoint 'a_m_y_hipster_01' { x = -219.201, y = 6562.82, z = 10.9706 } +spawnpoint 'a_m_y_hipster_02' { x = -45.1562, y = 6301.64, z = 31.6114 } +spawnpoint 'a_m_y_hipster_01' { x = -1004.77, y = 4854.32, z = 274.606 } +spawnpoint 'a_m_y_hipster_02' { x = -1580.01, y = 5173.3, z = 19.5813 } +spawnpoint 'a_m_y_hipster_01' { x = -1467.95, y = 5416.2, z = 23.5959 } +spawnpoint 'a_m_y_hipster_02' { x = -2359.31, y = 3243.83, z = 92.9037 } +spawnpoint 'a_m_y_hipster_01' { x = -2612.96, y = 3555.03, z = 4.85649 } +spawnpoint 'a_m_y_hipster_02' { x = -2083.27, y = 2616.94, z = 3.08396 } +spawnpoint 'a_m_y_hipster_01' { x = -524.471, y = 4195, z = 193.731 } +spawnpoint 'a_m_y_hipster_02' { x = -840.713, y = 4183.18, z = 215.29 } +spawnpoint 'a_m_y_hipster_01' { x = -1576.24, y = 2103.87, z = 67.576 } +spawnpoint 'a_m_y_hipster_02' { x = -1634.37, y = 209.816, z = 60.6413 } +spawnpoint 'a_m_y_hipster_01' { x = -1495.07, y = 142.697, z = 55.6527 } +spawnpoint 'a_m_y_hipster_02' { x = -1715.41, y = -197.722, z = 57.698 } +spawnpoint 'a_m_y_hipster_01' { x = -1181.07, y = -505.544, z = 35.5661 } +spawnpoint 'a_m_y_hipster_02' { x = -1712.37, y = -1082.91, z = 13.0801 } +spawnpoint 'a_m_y_hipster_01' { x = -1352.43, y = -1542.75, z = 4.42268 } +spawnpoint 'a_m_y_hipster_02' { x = -1756.89, y = 427.531, z = 127.685 } +spawnpoint 'a_m_y_hipster_01' { x = 3060.2, y = 2113.2, z = 1.6613 } +spawnpoint 'a_m_y_hipster_02' { x = 501.646, y = 5604.53, z = 797.91 } +spawnpoint 'a_m_y_hipster_01' { x = 714.109, y = 4151.15, z = 35.7792 } +spawnpoint 'a_m_y_hipster_02' { x = -103.651, y = -967.93, z = 296.52 } +spawnpoint 'a_m_y_hipster_01' { x = -265.333, y = -2419.35, z = 122.366 } +spawnpoint 'a_m_y_hipster_02' { x = 1788.25, y = 3890.34, z = 34.3849 } + +-- \ No newline at end of file diff --git a/resources/fivem-map-skater/__resource.lua b/resources/fivem-map-skater/__resource.lua new file mode 100644 index 0000000..1064a5a --- /dev/null +++ b/resources/fivem-map-skater/__resource.lua @@ -0,0 +1,3 @@ +resource_type 'map' { gameTypes = { fivem = true } } + +map 'map.lua' diff --git a/resources/fivem-map-skater/map.lua b/resources/fivem-map-skater/map.lua new file mode 100644 index 0000000..93a9069 --- /dev/null +++ b/resources/fivem-map-skater/map.lua @@ -0,0 +1,62 @@ +spawnpoint 'a_m_y_skater_01' { x = -802.311, y = 175.056, z = 72.8446 } +spawnpoint 'a_m_y_skater_02' { x = -9.96562, y = -1438.54, z = 31.1015 } +spawnpoint 'a_m_y_skater_01' { x = 0.916756, y = 528.485, z = 174.628 } +spawnpoint 'a_m_y_skater_02' { x = 1975.86, y = 3821.03, z = 33.4501 } +spawnpoint 'a_m_y_skater_01' { x = -181.615, y = 852.8, z = 232.701 } +spawnpoint 'a_m_y_skater_02' { x = 657.723, y = 457.342, z = 144.641 } +spawnpoint 'a_m_y_skater_01' { x = 134.387, y = 1150.31, z = 231.594 } +spawnpoint 'a_m_y_skater_02' { x = 726.14, y = 1196.91, z = 326.262 } +spawnpoint 'a_m_y_skater_01' { x = 740.792, y = 1283.62, z = 360.297 } +spawnpoint 'a_m_y_skater_02' { x = -437.009, y = 1059.59, z = 327.331 } +spawnpoint 'a_m_y_skater_01' { x = -428.771, y = 1596.8, z = 356.338 } +spawnpoint 'a_m_y_skater_02' { x = -1348.78, y = 723.87, z = 186.45 } +spawnpoint 'a_m_y_skater_01' { x = -1543.24, y = 830.069, z = 182.132 } +spawnpoint 'a_m_y_skater_02' { x = -2150.48, y = 222.019, z = 184.602 } +spawnpoint 'a_m_y_skater_01' { x = -3032.13, y = 22.2157, z = 10.1184 } +spawnpoint 'a_m_y_skater_02' { x = 3063.97, y = 5608.88, z = 209.245 } +spawnpoint 'a_m_y_skater_01' { x = -2614.35, y = 1872.49, z = 167.32 } +spawnpoint 'a_m_y_skater_02' { x = -1873.94, y = 2088.73, z = 140.994 } +spawnpoint 'a_m_y_skater_01' { x = -597.177, y = 2092.16, z = 131.413 } +spawnpoint 'a_m_y_skater_02' { x = 967.126, y = 2226.99, z = 54.0588 } +spawnpoint 'a_m_y_skater_01' { x = -338.043, y = 2829, z = 56.0871 } +spawnpoint 'a_m_y_skater_02' { x = 1082.25, y = -696.921, z = 58.0099 } +spawnpoint 'a_m_y_skater_01' { x = 1658.31, y = -13.9234, z = 169.992 } +spawnpoint 'a_m_y_skater_02' { x = 2522.98, y = -384.436, z = 92.9928 } +spawnpoint 'a_m_y_skater_01' { x = 2826.27, y = -656.489, z = 1.87841 } +spawnpoint 'a_m_y_skater_02' { x = 2851.12, y = 1467.5, z = 24.5554 } +spawnpoint 'a_m_y_skater_01' { x = 2336.33, y = 2535.39, z = 46.5177 } +spawnpoint 'a_m_y_skater_02' { x = 2410.46, y = 3077.88, z = 48.1529 } +spawnpoint 'a_m_y_skater_01' { x = 2451.15, y = 3768.37, z = 41.3477 } +spawnpoint 'a_m_y_skater_02' { x = 3337.78, y = 5174.8, z = 18.2108 } +spawnpoint 'a_m_y_skater_01' { x = -1119.33, y = 4978.52, z = 186.26 } +spawnpoint 'a_m_y_skater_02' { x = 2877.3, y = 5911.57, z = 369.618 } +spawnpoint 'a_m_y_skater_01' { x = 2942.1, y = 5306.73, z = 101.52 } +spawnpoint 'a_m_y_skater_02' { x = 2211.29, y = 5577.94, z = 53.872 } +spawnpoint 'a_m_y_skater_01' { x = 1602.39, y = 6623.02, z = 15.8417 } +spawnpoint 'a_m_y_skater_02' { x = 66.0113, y = 7203.58, z = 3.16 } +spawnpoint 'a_m_y_skater_01' { x = -219.201, y = 6562.82, z = 10.9706 } +spawnpoint 'a_m_y_skater_02' { x = -45.1562, y = 6301.64, z = 31.6114 } +spawnpoint 'a_m_y_skater_01' { x = -1004.77, y = 4854.32, z = 274.606 } +spawnpoint 'a_m_y_skater_02' { x = -1580.01, y = 5173.3, z = 19.5813 } +spawnpoint 'a_m_y_skater_01' { x = -1467.95, y = 5416.2, z = 23.5959 } +spawnpoint 'a_m_y_skater_02' { x = -2359.31, y = 3243.83, z = 92.9037 } +spawnpoint 'a_m_y_skater_01' { x = -2612.96, y = 3555.03, z = 4.85649 } +spawnpoint 'a_m_y_skater_02' { x = -2083.27, y = 2616.94, z = 3.08396 } +spawnpoint 'a_m_y_skater_01' { x = -524.471, y = 4195, z = 193.731 } +spawnpoint 'a_m_y_skater_02' { x = -840.713, y = 4183.18, z = 215.29 } +spawnpoint 'a_m_y_skater_01' { x = -1576.24, y = 2103.87, z = 67.576 } +spawnpoint 'a_m_y_skater_02' { x = -1634.37, y = 209.816, z = 60.6413 } +spawnpoint 'a_m_y_skater_01' { x = -1495.07, y = 142.697, z = 55.6527 } +spawnpoint 'a_m_y_skater_02' { x = -1715.41, y = -197.722, z = 57.698 } +spawnpoint 'a_m_y_skater_01' { x = -1181.07, y = -505.544, z = 35.5661 } +spawnpoint 'a_m_y_skater_02' { x = -1712.37, y = -1082.91, z = 13.0801 } +spawnpoint 'a_m_y_skater_01' { x = -1352.43, y = -1542.75, z = 4.42268 } +spawnpoint 'a_m_y_skater_02' { x = -1756.89, y = 427.531, z = 127.685 } +spawnpoint 'a_m_y_skater_01' { x = 3060.2, y = 2113.2, z = 1.6613 } +spawnpoint 'a_m_y_skater_02' { x = 501.646, y = 5604.53, z = 797.91 } +spawnpoint 'a_m_y_skater_01' { x = 714.109, y = 4151.15, z = 35.7792 } +spawnpoint 'a_m_y_skater_02' { x = -103.651, y = -967.93, z = 296.52 } +spawnpoint 'a_m_y_skater_01' { x = -265.333, y = -2419.35, z = 122.366 } +spawnpoint 'a_m_y_skater_02' { x = 1788.25, y = 3890.34, z = 34.3849 } + +-- \ No newline at end of file diff --git a/resources/fivem/__resource.lua b/resources/fivem/__resource.lua new file mode 100644 index 0000000..88045c4 --- /dev/null +++ b/resources/fivem/__resource.lua @@ -0,0 +1,3 @@ +resource_type 'gametype' { name = 'Freeroam' } + +client_script 'fivem_client.lua' \ No newline at end of file diff --git a/resources/fivem/fivem_client.lua b/resources/fivem/fivem_client.lua new file mode 100644 index 0000000..47a6ea8 --- /dev/null +++ b/resources/fivem/fivem_client.lua @@ -0,0 +1,9 @@ +AddEventHandler('onClientMapStart', function() + Citizen.Trace("ocms fivem\n") + + exports.spawnmanager:setAutoSpawn(true) + exports.spawnmanager:forceRespawn() + SetClockTime(24, 0, 0) + PauseClock(true) + Citizen.Trace("ocms fivem end\n") +end) \ No newline at end of file diff --git a/resources/initial/net_init.lua b/resources/initial/net_init.lua new file mode 100644 index 0000000..6665439 --- /dev/null +++ b/resources/initial/net_init.lua @@ -0,0 +1,85 @@ +local function launchGame() + -- TODO: replace with the actual game mode the joined session is running + --StartResource('citizen') + TriggerEvent('gameModeStarted') + + --StopResource('initial') +end + +local function showError(err) + ForceLoadingScreen(0) + SetMsgForLoadingScreen(err) + + echo(err .. "\n") + + return "exit" +end + +CreateThread(function() + Wait(50) + + AllowThisScriptToBePaused(false) + SetNoResprays(false) + ThisScriptIsSafeForNetworkGame() + + if IsPlayerPlaying(GetPlayerIndex()) then + SetPlayerControl(GetPlayerIndex(), false) + end + + -- setup default callbacks + exports.session:reset() + + AddEventHandler('sessionStateChanged', function(state) + if state == 'find' then + SetLoadingText('Finding games...') + elseif state == 'host' then + SetLoadingText('Creating game...') + end + end) + + AddEventHandler('sessionJoining', function(cur, max, hostName) + SetLoadingText('Joining game ' .. cur .. ' of ' .. max .. '... (' .. hostName .. ')') + end) + + AddEventHandler('sessionJoined', launchGame) + AddEventHandler('sessionHosted', launchGame) + + AddEventHandler('sessionHostFailed', function(err) + echo("hosting game failed: " .. err .. "\n") + showError("NICON_MT") + + ShutdownAndLaunchSinglePlayerGame() + end) + + local sessionList + local curJoinSession = 0 + + local joinFailed = function() + exports.session:hostSession(16, 32) + end + + AddEventHandler('sessionJoinFailed', function() + curJoinSession = curJoinSession + 1 + + if curJoinSession > #sessionList then + joinFailed() + return + end + + exports.session:joinSession(sessionList[curJoinSession]) + end) + + AddEventHandler('sessionsFound', function(sessions) + if #sessions == 0 then + joinFailed() + return + end + + sessionList = sessions + + curJoinSession = 1 + exports.session:joinSession(sessionList[curJoinSession]) + end) + + exports.session:findSessions(16) +end) diff --git a/run.bat b/run.bat new file mode 100644 index 0000000..0b06d7a --- /dev/null +++ b/run.bat @@ -0,0 +1,2 @@ +@echo off +start CitizenMP.Server.exe %* \ No newline at end of file diff --git a/run.sh b/run.sh new file mode 100644 index 0000000..af1cca6 --- /dev/null +++ b/run.sh @@ -0,0 +1,4 @@ +#!/bin/bash +fx_root=`dirname "$(readlink -f "$0")"` + +MONO_PATH=$fx_root/mono/ mono $fx_root/CitizenMP.Server.exe $* \ No newline at end of file diff --git a/system/MessagePack.lua b/system/MessagePack.lua new file mode 100644 index 0000000..685d8cf --- /dev/null +++ b/system/MessagePack.lua @@ -0,0 +1,1205 @@ +-- +-- lua-MessagePack : +-- + +local r, jit = nil, nil + +local SIZEOF_NUMBER = 8 +local NUMBER_INTEGRAL = false +if not jit then + -- Lua 5.1 & 5.2 + local loadstring = loadstring or load + --local luac = string.dump(loadstring "a = 1") + --local header = { luac:sub(1, 12):byte(1, 12) } + SIZEOF_NUMBER = 4 + NUMBER_INTEGRAL = 1 == 0 +end + +--[[local assert = assert +local error = error +local pairs = pairs +local pcall = pcall +local setmetatable = setmetatable +local tostring = tostring +local type = type]] +local char = string.char +local floor = function(a) return a end--math.floor +--local frexp = math.frexp +--local ldexp = math.ldexp +local huge = math.huge +local tconcat = table.concat + +--[[ debug only +local format = require'string'.format +local function hexadump (s) + return (s:gsub('.', function (c) return format('%02X ', c:byte()) end)) +end +--]] + +local function utf8_to_utf16(s) + local utf8 = clr.System.Text.Encoding.UTF8 + local bytes : byte[] = clr.System.Byte[#s] + + for i = 0, #s - 1 do + bytes[i] = string.byte(s, i + 1) + end + + return utf8:GetString(bytes) +end + +local function utf16_to_utf8(s) + local utf8 = clr.System.Text.Encoding.UTF8 + local StringBuilder = clr.System.Text.StringBuilder + local bytes = utf8:GetBytes(s) + + local sb = StringBuilder(bytes.Length) + + for i = 0, bytes.Length - 1 do + sb:Append(cast(char, bytes[i])) + end + + return sb:ToString() +end + +local m = {} + +--[[ debug only +m.hexadump = hexadump +--]] + +local function argerror (caller, narg, extramsg) + error("bad argument #" .. tostring(narg) .. " to " + .. caller .. " (" .. extramsg .. ")") +end + +local function typeerror (caller, narg, arg, tname) + argerror(caller, narg, tname .. " expected, got " .. type(arg)) +end + +local function checktype (caller, narg, arg, tname) + if type(arg) ~= tname then + typeerror(caller, narg, arg, tname) + end +end + +--[[local packers = setmetatable({}, { + __index = function (t, k) error("pack '" .. k .. "' is unimplemented") end +})]] +local packers = {} +m.packers = packers + +packers['nil'] = function (buffer) + buffer[#buffer+1] = char(0xC0) -- nil +end + +packers['boolean'] = function (buffer, bool) + if bool then + buffer[#buffer+1] = char(0xC3) -- true + else + buffer[#buffer+1] = char(0xC2) -- false + end +end + +packers['string_compat'] = function (buffer, str) + str = utf16_to_utf8(str) + + local n = #str + if n <= 0x1F then + buffer[#buffer+1] = char(0xA0 + n) -- fixstr + elseif n <= 0xFFFF then + buffer[#buffer+1] = char(0xDA, -- str16 + (n / 0x100), + n % 0x100) + elseif n <= 0xFFFFFFFF then + buffer[#buffer+1] = char(0xDB, -- str32 + (n / 0x1000000), + (n / 0x10000) % 0x100, + (n / 0x100) % 0x100, + n % 0x100) + else + error"overflow in pack 'string_compat'" + end + buffer[#buffer+1] = str +end + +packers['_string'] = function (buffer, str) + str = utf16_to_utf8(str) + + local n = #str + if n <= 0x1F then + buffer[#buffer+1] = char(0xA0 + n) -- fixstr + elseif n <= 0xFF then + buffer[#buffer+1] = char(0xD9, -- str8 + n) + elseif n <= 0xFFFF then + buffer[#buffer+1] = char(0xDA, -- str16 + (n / 0x100), + n % 0x100) + elseif n <= 0xFFFFFFFF then + buffer[#buffer+1] = char(0xDB, -- str32 + (n / 0x1000000), + (n / 0x10000) % 0x100, + (n / 0x100) % 0x100, + n % 0x100) + else + error"overflow in pack 'string'" + end + buffer[#buffer+1] = str +end + +packers['binary'] = function (buffer, str) + str = utf16_to_utf8(str) + + local n = #str + if n <= 0xFF then + buffer[#buffer+1] = char(0xC4, -- bin8 + n) + elseif n <= 0xFFFF then + buffer[#buffer+1] = char(0xC5, -- bin16 + (n / 0x100), + n % 0x100) + elseif n <= 0xFFFFFFFF then + buffer[#buffer+1] = char(0xC6, -- bin32 + (n / 0x1000000), + (n / 0x10000) % 0x100, + (n / 0x100) % 0x100, + n % 0x100) + else + error"overflow in pack 'binary'" + end + buffer[#buffer+1] = str +end + +local set_string = function (str) + if str == 'string_compat' then + packers['string'] = packers['string_compat'] + elseif str == 'string' then + packers['string'] = packers['_string'] + elseif str == 'binary' then + packers['string'] = packers['binary'] + else + argerror('set_string', 1, "invalid option '" .. str .."'") + end +end +m.set_string = set_string + +packers['map'] = function (buffer, tbl, n) + if n <= 0x0F then + buffer[#buffer+1] = char(0x80 + n) -- fixmap + elseif n <= 0xFFFF then + buffer[#buffer+1] = char(0xDE, -- map16 + (n / 0x100), + n % 0x100) + elseif n <= 0xFFFFFFFF then + buffer[#buffer+1] = char(0xDF, -- map32 + (n / 0x1000000), + (n / 0x10000) % 0x100, + (n / 0x100) % 0x100, + n % 0x100) + else + error"overflow in pack 'map'" + end + for k, v in pairs(tbl) do + packers[type(k)](buffer, k) + packers[type(v)](buffer, v) + end +end + +packers['array'] = function (buffer, tbl, n) + if n <= 0x0F then + buffer[#buffer+1] = char(0x90 + n) -- fixarray + elseif n <= 0xFFFF then + buffer[#buffer+1] = char(0xDC, -- array16 + (n / 0x100), + n % 0x100) + elseif n <= 0xFFFFFFFF then + buffer[#buffer+1] = char(0xDD, -- array32 + (n / 0x1000000), + (n / 0x10000) % 0x100, + (n / 0x100) % 0x100, + n % 0x100) + else + error"overflow in pack 'array'" + end + for i = 1, n do + local v = tbl[i] + packers[type(v)](buffer, v) + end +end + +local set_array = function (array) + if array == 'without_hole' then + packers['_table'] = function (buffer, tbl) + local is_map, n, max = false, 0, 0 + for k in pairs(tbl) do + if type(k) == 'number' and k > 0 then + if k > max then + max = k + end + else + is_map = true + end + n = n + 1 + end + if max ~= n then -- there are holes + is_map = true + end + if is_map then + return packers['map'](buffer, tbl, n) + else + return packers['array'](buffer, tbl, n) + end + end + elseif array == 'with_hole' then + packers['_table'] = function (buffer, tbl) + local is_map, n, max = false, 0, 0 + for k in pairs(tbl) do + if type(k) == 'number' and k > 0 then + if k > max then + max = k + end + else + is_map = true + end + n = n + 1 + end + if is_map then + return packers['map'](buffer, tbl, n) + else + return packers['array'](buffer, tbl, max) + end + end + elseif array == 'always_as_map' then + packers['_table'] = function(buffer, tbl) + local n = 0 + for k in pairs(tbl) do + n = n + 1 + end + return packers['map'](buffer, tbl, n) + end + else + argerror('set_array', 1, "invalid option '" .. array .."'") + end +end +m.set_array = set_array + +packers['table'] = function (buffer, tbl) + return packers['_table'](buffer, tbl) +end + +packers['unsigned'] = function (buffer, n) + if n >= 0 then + if n <= 0x7F then + buffer[#buffer+1] = char(n) -- fixnum_pos + elseif n <= 0xFF then + buffer[#buffer+1] = char(0xCC, -- uint8 + n) + elseif n <= 0xFFFF then + buffer[#buffer+1] = char(0xCD, -- uint16 + (n / 0x100), + n % 0x100) + elseif n <= 0xFFFFFFFF then + buffer[#buffer+1] = char(0xCE, -- uint32 + (n / 0x1000000), + (n / 0x10000) % 0x100, + (n / 0x100) % 0x100, + n % 0x100) + else + buffer[#buffer+1] = char(0xCF, -- uint64 + 0, -- only 53 bits from double + (n / 0x1000000000000) % 0x100, + (n / 0x10000000000) % 0x100, + (n / 0x100000000) % 0x100, + (n / 0x1000000) % 0x100, + (n / 0x10000) % 0x100, + (n / 0x100) % 0x100, + n % 0x100) + end + else + if n >= -0x20 then + buffer[#buffer+1] = char(0x100 + n) -- fixnum_neg + elseif n >= -0x80 then + buffer[#buffer+1] = char(0xD0, -- int8 + 0x100 + n) + elseif n >= -0x8000 then + n = 0x10000 + n + buffer[#buffer+1] = char(0xD1, -- int16 + (n / 0x100), + n % 0x100) + elseif n >= -0x7FFFFFFE then + n = 0x100000000 + n + buffer[#buffer+1] = char(0xD2, -- int32 + (n / 0x1000000), + (n / 0x10000) % 0x100, + (n / 0x100) % 0x100, + n % 0x100) + else + buffer[#buffer+1] = char(0xD3, -- int64 + 0xFF, -- only 53 bits from double + (n / 0x1000000000000) % 0x100, + (n / 0x10000000000) % 0x100, + (n / 0x100000000) % 0x100, + (n / 0x1000000) % 0x100, + (n / 0x10000) % 0x100, + (n / 0x100) % 0x100, + n % 0x100) + end + end +end + +packers['signed'] = function (buffer, n) + if n >= 0 then + if n <= 0x7F then + buffer[#buffer+1] = char(n) -- fixnum_pos + elseif n <= 0x7FFF then + buffer[#buffer+1] = char(0xD1, -- int16 + (n / 0x100), + n % 0x100) + elseif n <= 0x7FFFFFFF then + buffer[#buffer+1] = char(0xD2, -- int32 + (n / 0x1000000), + (n / 0x10000) % 0x100, + (n / 0x100) % 0x100, + n % 0x100) + else + buffer[#buffer+1] = char(0xD3, -- int64 + 0, -- only 53 bits from double + (n / 0x1000000000000) % 0x100, + (n / 0x10000000000) % 0x100, + (n / 0x100000000) % 0x100, + (n / 0x1000000) % 0x100, + (n / 0x10000) % 0x100, + (n / 0x100) % 0x100, + n % 0x100) + end + else + if n >= -0x20 then + buffer[#buffer+1] = char(0xE0 + 0x20 + n) -- fixnum_neg + elseif n >= -0x80 then + buffer[#buffer+1] = char(0xD0, -- int8 + 0x100 + n) + elseif n >= -0x8000 then + n = 0x10000 + n + buffer[#buffer+1] = char(0xD1, -- int16 + (n / 0x100), + n % 0x100) + elseif n >= -0x7FFFFFFE then + n = 0x100000000 + n + buffer[#buffer+1] = char(0xD2, -- int32 + (n / 0x1000000), + (n / 0x10000) % 0x100, + (n / 0x100) % 0x100, + n % 0x100) + else + buffer[#buffer+1] = char(0xD3, -- int64 + 0xFF, -- only 53 bits from double + (n / 0x1000000000000) % 0x100, + (n / 0x10000000000) % 0x100, + (n / 0x100000000) % 0x100, + (n / 0x1000000) % 0x100, + (n / 0x10000) % 0x100, + (n / 0x100) % 0x100, + n % 0x100) + end + end +end + +local set_integer = function (integer) + if integer == 'unsigned' then + packers['integer'] = packers['unsigned'] + elseif integer == 'signed' then + packers['integer'] = packers['signed'] + else + argerror('set_integer', 1, "invalid option '" .. integer .."'") + end +end +m.set_integer = set_integer + +packers['float'] = function (buffer, n) + local sign = 0 + if n < 0.0 then + sign = 0x80 + n = -n + end + local mant, expo = frexp(n) + if mant ~= mant then + buffer[#buffer+1] = char(0xCA, -- nan + 0xFF, 0x88, 0x00, 0x00) + elseif mant == huge or expo > 0x80 then + if sign == 0 then + buffer[#buffer+1] = char(0xCA, -- inf + 0x7F, 0x80, 0x00, 0x00) + else + buffer[#buffer+1] = char(0xCA, -- -inf + 0xFF, 0x80, 0x00, 0x00) + end + elseif (mant == 0.0 and expo == 0) or expo < -0x7E then + buffer[#buffer+1] = char(0xCA, -- zero + sign, 0x00, 0x00, 0x00) + else + expo = expo + 0x7E + mant = (mant * 2.0 - 1.0) * ldexp(0.5, 24) + buffer[#buffer+1] = char(0xCA, + sign + (expo / 0x2), + (expo % 0x2) * 0x80 + (mant / 0x10000), + (mant / 0x100) % 0x100, + mant % 0x100) + end +end + +local mf = function(f) + local a = math.floor(f) + + return a +end + +packers['double'] = function (buffer, n) + local sign = 0 + if n < 0.0 then + sign = 0x80 + n = -n + end + local mant, expo = frexp(n) + + if mant ~= mant then + buffer[#buffer+1] = char(0xCB, -- nan + 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) + elseif mant == huge then + if sign == 0 then + buffer[#buffer+1] = char(0xCB, -- inf + 0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) + else + buffer[#buffer+1] = char(0xCB, -- -inf + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) + end + elseif mant == 0.0 and expo == 0 then + buffer[#buffer+1] = char(0xCB, -- zero + sign, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) + else + expo = expo + 0x3FE + mant = (mant * 2.0 - 1.0) * ldexp(0.5, 53) + + buffer[#buffer+1] = char(0xCB, + sign + s, + ff, + f2, + f3, + f4, + f5, + f6, + mant % 0x100) + end +end + +local set_number = function (number) + if number == 'integer' then + packers['number'] = packers['signed'] + elseif number == 'float' then + packers['number'] = function (buffer, n) + if math.floor(n) ~= n or n ~= n or n > 3.40282347e+38 or n < -3.40282347e+38 then + return packers['float'](buffer, n) + else + return packers['integer'](buffer, n) + end + end + elseif number == 'double' then + packers['number'] = function (buffer, n) + if math.floor(n) ~= n or n ~= n or n == huge or n == -huge then + return packers['double'](buffer, n) + else + return packers['integer'](buffer, n) + end + end + else + argerror('set_number', 1, "invalid option '" .. number .."'") + end +end +m.set_number = set_number + +for k = 0, 4 do + local n = 2^k + local fixext = 0xD4 + k + packers['fixext' .. n] = function (buffer, tag, data) + assert(#data == n, "bad length for fixext" .. n) + buffer[#buffer+1] = char(fixext, + tag < 0 and tag + 0x100 or tag) + buffer[#buffer+1] = data + end +end + +packers['ext'] = function (buffer, tag, data) + local n = #data + if n <= 0xFF then + buffer[#buffer+1] = char(0xC7, -- ext8 + n, + tag < 0 and tag + 0x100 or tag) + elseif n <= 0xFFFF then + buffer[#buffer+1] = char(0xC8, -- ext16 + (n / 0x100), + n % 0x100, + tag < 0 and tag + 0x100 or tag) + elseif n <= 0xFFFFFFFF then + buffer[#buffer+1] = char(0xC9, -- ext&32 + (n / 0x1000000), + (n / 0x10000) % 0x100, + (n / 0x100) % 0x100, + n % 0x100, + tag < 0 and tag + 0x100 or tag) + else + error"overflow in pack 'ext'" + end + buffer[#buffer+1] = data +end + +function m.pack (data) + local buffer = {} + + local packer = packers[type(data)] + packer(buffer, data) + + return table.concat(buffer) +end + + +local types_map = setmetatable({ + [0xC0] = 'nil', + [0xC2] = 'false', + [0xC3] = 'true', + [0xC4] = 'bin8', + [0xC5] = 'bin16', + [0xC6] = 'bin32', + [0xC7] = 'ext8', + [0xC8] = 'ext16', + [0xC9] = 'ext32', + [0xCA] = 'float', + [0xCB] = 'double', + [0xCC] = 'uint8', + [0xCD] = 'uint16', + [0xCE] = 'uint32', + [0xCF] = 'uint64', + [0xD0] = 'int8', + [0xD1] = 'int16', + [0xD2] = 'int32', + [0xD3] = 'int64', + [0xD4] = 'fixext1', + [0xD5] = 'fixext2', + [0xD6] = 'fixext4', + [0xD7] = 'fixext8', + [0xD8] = 'fixext16', + [0xD9] = 'str8', + [0xDA] = 'str16', + [0xDB] = 'str32', + [0xDC] = 'array16', + [0xDD] = 'array32', + [0xDE] = 'map16', + [0xDF] = 'map32', +}, { __index = function (t, k) + if k < 0xC0 then + if k < 0x80 then + return 'fixnum_pos' + elseif k < 0x90 then + return 'fixmap' + elseif k < 0xA0 then + return 'fixarray' + else + return 'fixstr' + end + elseif k > 0xDF then + return 'fixnum_neg' + else + return 'reserved' .. k + end +end }) +m.types_map = types_map + +local unpackers = setmetatable({}, { + __index = function (t, k) + local g = rawget(t, k) + + if g then + return g + end + + error("unpack '" .. k .. "' is unimplemented") + end +}) +m.unpackers = unpackers + +local function unpack_array (c, n) + local t = {} + local decode = unpackers['any'] + for i = 1, n do + t[i] = decode(c) + end + return t +end + +local function unpack_map (c, n) + local t = {} + local decode = unpackers['any'] + for i = 1, n do + local k = decode(c) + local v = decode(c) + + if k then + t[k] = v + end + end + return t +end + +unpackers['any'] = function (c) + local s, i, j = c.s, c.i, c.j + if i > j then + c:underflow(i) + s, i, j = c.s, c.i, c.j + end + local val = s:sub(i, i):byte() + c.i = i+1 + return unpackers[types_map[val]](c, val) +end + +unpackers['nil'] = function () + return nil +end + +unpackers['false'] = function () + return false +end + +unpackers['true'] = function () + return true +end + +unpackers['float'] = function (c) + local s, i, j = c.s, c.i, c.j + if i+3 > j then + c:underflow(i+3) + s, i, j = c.s, c.i, c.j + end + local b1, b2, b3, b4 = s:sub(i, i+3):byte(1, 4) + local sign = b1 > 0x7F + local expo = (b1 % 0x80) * 0x2 + (b2 / 0x80) + local mant = ((b2 % 0x80) * 0x100 + b3) * 0x100 + b4 + if sign then + sign = -1 + else + sign = 1 + end + local n + if mant == 0 and expo == 0 then + n = sign * 0.0 + elseif expo == 0xFF then + if mant == 0 then + n = sign * huge + else + n = 0.0/0.0 + end + else + n = sign * ldexp(1.0 + mant / 0x800000, expo - 0x7F) + end + c.i = i+4 + return n +end + +unpackers['double'] = function (c) + local s, i, j = c.s, c.i, c.j + if i+7 > j then + c:underflow(i+7) + s, i, j = c.s, c.i, c.j + end + local b1, b2, b3, b4, b5, b6, b7, b8 = s:sub(i, i+7):byte(1, 8) + + local arr = clr.System.Byte[]{ b8, b7, b6, b5, b4, b3, b2, b1 } + + c.i = i+8 + + return clr.System.BitConverter.ToDouble(arr, 0) + end + +unpackers['fixnum_pos'] = function (c, val) + return val +end + +unpackers['uint8'] = function (c) + local s, i, j = c.s, c.i, c.j + if i > j then + c:underflow(i) + s, i, j = c.s, c.i, c.j + end + local b1 = s:sub(i, i):byte() + c.i = i+1 + return b1 +end + +unpackers['uint16'] = function (c) + local s, i, j = c.s, c.i, c.j + if i+1 > j then + c:underflow(i+1) + s, i, j = c.s, c.i, c.j + end + local b1, b2 = s:sub(i, i+1):byte(1, 2) + c.i = i+2 + return b1 * 0x100 + b2 +end + +unpackers['uint32'] = function (c) + local s, i, j = c.s, c.i, c.j + if i+3 > j then + c:underflow(i+3) + s, i, j = c.s, c.i, c.j + end + local b1, b2, b3, b4 = s:sub(i, i+3):byte(1, 4) + c.i = i+4 + return ((b1 * 0x100 + b2) * 0x100 + b3) * 0x100 + b4 +end + +unpackers['uint64'] = function (c) + local s, i, j = c.s, c.i, c.j + if i+7 > j then + c:underflow(i+7) + s, i, j = c.s, c.i, c.j + end + local b1, b2, b3, b4, b5, b6, b7, b8 = s:sub(i, i+7):byte(1, 8) + c.i = i+8 + return ((((((b1 * 0x100 + b2) * 0x100 + b3) * 0x100 + b4) * 0x100 + b5) * 0x100 + b6) * 0x100 + b7) * 0x100 + b8 +end + +unpackers['fixnum_neg'] = function (c, val) + return val - 0x100 +end + +unpackers['int8'] = function (c) + local s, i, j = c.s, c.i, c.j + if i > j then + c:underflow(i) + s, i, j = c.s, c.i, c.j + end + local b1 = s:sub(i, i):byte() + c.i = i+1 + if b1 < 0x80 then + return b1 + else + return b1 - 0x100 + end +end + +unpackers['int16'] = function (c) + local s, i, j = c.s, c.i, c.j + if i+1 > j then + c:underflow(i+1) + s, i, j = c.s, c.i, c.j + end + local b1, b2 = s:sub(i, i+1):byte(1, 2) + c.i = i+2 + if b1 < 0x80 then + return b1 * 0x100 + b2 + else + return ((b1 - 0xFF) * 0x100 + (b2 - 0xFF)) - 1 + end +end + +unpackers['int32'] = function (c) + local s, i, j = c.s, c.i, c.j + if i+3 > j then + c:underflow(i+3) + s, i, j = c.s, c.i, c.j + end + local b1, b2, b3, b4 = s:sub(i, i+3):byte(1, 4) + c.i = i+4 + if b1 < 0x80 then + return ((b1 * 0x100 + b2) * 0x100 + b3) * 0x100 + b4 + else + return ((((b1 - 0xFF) * 0x100 + (b2 - 0xFF)) * 0x100 + (b3 - 0xFF)) * 0x100 + (b4 - 0xFF)) - 1 + end +end + +unpackers['int64'] = function (c) + local s, i, j = c.s, c.i, c.j + if i+7 > j then + c:underflow(i+7) + s, i, j = c.s, c.i, c.j + end + local b1, b2, b3, b4, b5, b6, b7, b8 = s:sub(i, i+7):byte(1, 8) + c.i = i+8 + if b1 < 0x80 then + return ((((((b1 * 0x100 + b2) * 0x100 + b3) * 0x100 + b4) * 0x100 + b5) * 0x100 + b6) * 0x100 + b7) * 0x100 + b8 + else + return ((((((((b1 - 0xFF) * 0x100 + (b2 - 0xFF)) * 0x100 + (b3 - 0xFF)) * 0x100 + (b4 - 0xFF)) * 0x100 + (b5 - 0xFF)) * 0x100 + (b6 - 0xFF)) * 0x100 + (b7 - 0xFF)) * 0x100 + (b8 - 0xFF)) - 1 + end +end + +unpackers['fixstr'] = function (c, val) + local s, i, j = c.s, c.i, c.j + local n = val % 0x20 + local e = i+n-1 + if e > j then + c:underflow(e) + s, i, j = c.s, c.i, c.j + e = i+n-1 + end + c.i = i+n + return utf8_to_utf16(s:sub(i, e)) +end + +unpackers['str8'] = function (c) + local s, i, j = c.s, c.i, c.j + if i > j then + c:underflow(i) + s, i, j = c.s, c.i, c.j + end + local n = s:sub(i, i):byte() + i = i+1 + c.i = i + local e = i+n-1 + if e > j then + c:underflow(e) + s, i, j = c.s, c.i, c.j + e = i+n-1 + end + c.i = i+n + return utf8_to_utf16(s:sub(i, e)) +end + +unpackers['str16'] = function (c) + local s, i, j = c.s, c.i, c.j + if i+1 > j then + c:underflow(i+1) + s, i, j = c.s, c.i, c.j + end + local b1, b2 = s:sub(i, i+1):byte(1, 2) + i = i+2 + c.i = i + local n = b1 * 0x100 + b2 + local e = i+n-1 + + if e > j then + c:underflow(e) + s, i, j = c.s, c.i, c.j + e = i+n-1 + end + c.i = i+n + return utf8_to_utf16(s:sub(i, e)) +end + +unpackers['str32'] = function (c) + local s, i, j = c.s, c.i, c.j + if i+3 > j then + c:underflow(i+3) + s, i, j = c.s, c.i, c.j + end + local b1, b2, b3, b4 = s:sub(i, i+3):byte(1, 4) + i = i+4 + c.i = i + local n = ((b1 * 0x100 + b2) * 0x100 + b3) * 0x100 + b4 + local e = i+n-1 + if e > j then + c:underflow(e) + s, i, j = c.s, c.i, c.j + e = i+n-1 + end + c.i = i+n + return utf8_to_utf16(s:sub(i, e)) +end + +unpackers['bin8'] = unpackers['str8'] +unpackers['bin16'] = unpackers['str16'] +unpackers['bin32'] = unpackers['str32'] + +unpackers['fixarray'] = function (c, val) + return unpack_array(c, val % 0x10) +end + +unpackers['array16'] = function (c) + local s, i, j = c.s, c.i, c.j + if i+1 > j then + c:underflow(i+1) + s, i, j = c.s, c.i, c.j + end + local b1, b2 = s:sub(i, i+1):byte(1, 2) + c.i = i+2 + return unpack_array(c, b1 * 0x100 + b2) +end + +unpackers['array32'] = function (c) + local s, i, j = c.s, c.i, c.j + if i+3 > j then + c:underflow(i+3) + s, i, j = c.s, c.i, c.j + end + local b1, b2, b3, b4 = s:sub(i, i+3):byte(1, 4) + c.i = i+4 + return unpack_array(c, ((b1 * 0x100 + b2) * 0x100 + b3) * 0x100 + b4) +end + +unpackers['fixmap'] = function (c, val) + return unpack_map(c, val % 0x10) +end + +unpackers['map16'] = function (c) + local s, i, j = c.s, c.i, c.j + if i+1 > j then + c:underflow(i+1) + s, i, j = c.s, c.i, c.j + end + local b1, b2 = s:sub(i, i+1):byte(1, 2) + c.i = i+2 + return unpack_map(c, b1 * 0x100 + b2) +end + +unpackers['map32'] = function (c) + local s, i, j = c.s, c.i, c.j + if i+3 > j then + c:underflow(i+3) + s, i, j = c.s, c.i, c.j + end + local b1, b2, b3, b4 = s:sub(i, i+3):byte(1, 4) + c.i = i+4 + return unpack_map(c, ((b1 * 0x100 + b2) * 0x100 + b3) * 0x100 + b4) +end + +function m.build_ext (tag, data) + return nil +end + +for k = 0, 4 do + local n = 2^k + unpackers['fixext' .. n] = function (c) + local s, i, j = c.s, c.i, c.j + if i > j then + c:underflow(i) + s, i, j = c.s, c.i, c.j + end + local tag = s:sub(i, i):byte() + i = i+1 + c.i = i + local e = i+n-1 + if e > j then + c:underflow(e) + s, i, j = c.s, c.i, c.j + e = i+n-1 + end + c.i = i+n + return m.build_ext(tag < 0x80 and tag or tag - 0x100, s:sub(i, e)) + end +end + +unpackers['ext8'] = function (c) + local s, i, j = c.s, c.i, c.j + if i > j then + c:underflow(i) + s, i, j = c.s, c.i, c.j + end + local n = s:sub(i, i):byte() + i = i+1 + c.i = i + if i > j then + c:underflow(i) + s, i, j = c.s, c.i, c.j + end + local tag = s:sub(i, i):byte() + i = i+1 + c.i = i + local e = i+n-1 + if e > j then + c:underflow(e) + s, i, j = c.s, c.i, c.j + e = i+n-1 + end + c.i = i+n + return m.build_ext(tag < 0x80 and tag or tag - 0x100, s:sub(i, e)) +end + +unpackers['ext16'] = function (c) + local s, i, j = c.s, c.i, c.j + if i+1 > j then + c:underflow(i+1) + s, i, j = c.s, c.i, c.j + end + local b1, b2 = s:sub(i, i+1):byte(1, 2) + i = i+2 + c.i = i + local n = b1 * 0x100 + b2 + if i > j then + c:underflow(i) + s, i, j = c.s, c.i, c.j + end + local tag = s:sub(i, i):byte() + i = i+1 + c.i = i + local e = i+n-1 + if e > j then + c:underflow(e) + s, i, j = c.s, c.i, c.j + e = i+n-1 + end + c.i = i+n + return m.build_ext(tag < 0x80 and tag or tag - 0x100, s:sub(i, e)) +end + +unpackers['ext32'] = function (c) + local s, i, j = c.s, c.i, c.j + if i+3 > j then + c:underflow(i+3) + s, i, j = c.s, c.i, c.j + end + local b1, b2, b3, b4 = s:sub(i, i+3):byte(1, 4) + i = i+4 + c.i = i + local n = ((b1 * 0x100 + b2) * 0x100 + b3) * 0x100 + b4 + if i > j then + c:underflow(i) + s, i, j = c.s, c.i, c.j + end + local tag = s:sub(i, i):byte() + i = i+1 + c.i = i + local e = i+n-1 + if e > j then + c:underflow(e) + s, i, j = c.s, c.i, c.j + e = i+n-1 + end + c.i = i+n + return m.build_ext(tag < 0x80 and tag or tag - 0x100, s:sub(i, e)) +end + + +local function cursor_string (str) + return { + s = str, + i = 1, + j = #str, + underflow = function (self) + error "missing bytes" + end, + } +end + +local function cursor_loader (ld) + return { + s = '', + i = 1, + j = 0, + underflow = function (self, e) + self.s = self.s:sub(self.i) + e = e - self.i + 1 + self.i = 1 + self.j = 0 + while e > self.j do + local chunk = ld() + if not chunk then + error "missing bytes" + end + self.s = self.s .. chunk + self.j = #self.s + end + end, + } +end + +function m.unpack (s) + checktype('unpack', 1, s, 'string') + + --[[for i = 1, #s do + print(s:byte(i)) + end]] + + local cursor = cursor_string(s) + local data = unpackers['any'](cursor) + if cursor.i < cursor.j then + --error "extra bytes" + end + + return data +end + +function m.unpacker (src) + if type(src) == 'string' then + local cursor = cursor_string(src) + return function () + if cursor.i <= cursor.j then + return cursor.i, unpackers['any'](cursor) + end + end + elseif type(src) == 'function' then + local cursor = cursor_loader(src) + return function () + if cursor.i > cursor.j then + pcall(cursor.underflow, cursor, cursor.i) + end + if cursor.i <= cursor.j then + return true, unpackers['any'](cursor) + end + end + else + argerror('unpacker', 1, "string or function expected, got " .. type(src)) + end +end + +set_string'string_compat' +set_integer'signed' +if NUMBER_INTEGRAL then + set_number'integer' +elseif SIZEOF_NUMBER == 4 then + set_number'float' +else + set_number'double' +end +set_array'without_hole' + +m._VERSION = "0.3.1" +m._DESCRIPTION = "lua-MessagePack : a pure Lua implementation" +m._COPYRIGHT = "Copyright (c) 2012-2014 Francois Perrad" + +msgpack = m + +-- +-- This library is licensed under the terms of the MIT/X11 license, +-- like Lua itself. +-- + +-- +-- CitizenMP extensions follow +-- + +local EXT_CLOSURE = 1 + +m.packers['function'] = function(buffer, func) + local ref, inst, res = GetFuncRef(func) + + m.packers['ext'](buffer, EXT_CLOSURE, char( + (ref / 0x1000000), + (ref / 0x10000) % 0x100, + (ref / 0x100) % 0x100, + ref % 0x100) .. + char( + (inst / 0x1000000), + (inst / 0x10000) % 0x100, + (inst / 0x100) % 0x100, + inst % 0x100) + .. res) +end + +m.packers['userdata'] = function(buffer, udata) + --[[local ref, res = GetFuncRefByData(udata) + + m.packers['ext'](buffer, EXT_CLOSURE, char( + (ref / 0x1000000), + (ref / 0x10000) % 0x100, + (ref / 0x100) % 0x100, + ref % 0x100) .. res)]] + + error('unsupported pending NLua fixes') +end + +m.build_ext = function(tag, data) + if tag == EXT_CLOSURE then + local cursor = cursor_string(data) + local ref = unpackers['int32'](cursor) + local inst = unpackers['uint32'](cursor) + local res = data:sub(9) + + return GetFuncFromRef(ref, inst, res) + end +end diff --git a/system/dkjson.lua b/system/dkjson.lua new file mode 100644 index 0000000..43fac82 --- /dev/null +++ b/system/dkjson.lua @@ -0,0 +1,709 @@ +-- Module options: +local always_try_using_lpeg = true +local register_global_module_table = true +local global_module_name = 'json' + +--[==[ + +David Kolf's JSON module for Lua 5.1/5.2 + +Version 2.5 + + +For the documentation see the corresponding readme.txt or visit +. + +You can contact the author by sending an e-mail to 'david' at the +domain 'dkolf.de'. + + +Copyright (C) 2010-2013 David Heiko Kolf + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +--]==] + +-- global dependencies: +--local pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset = +-- pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset +--local error, require, pcall, select = error, require, pcall, select +local floor, huge = math.floor, math.huge +local strrep, gsub, gsubf, strsub, strbyte, strchar, strfind, strlen, strformat = + string.rep, gsub, gsubf, ssub, string.byte, string.char, + sfind, string.len, string.format +local strmatch = string.match +local concat = table.concat + +local json = { version = "dkjson 2.5" } + +if register_global_module_table then + _G[global_module_name] = json +end + +local _ENV = nil -- blocking globals in Lua 5.2 + +pcall (function() + -- Enable access to blocked metatables. + -- Don't worry, this module doesn't change anything in them. + local debmeta = require "debug".getmetatable + if debmeta then getmetatable = debmeta end +end) + +json.null = setmetatable ({}, { + __tojson = function () return "null" end +}) + +local function isarray (tbl) + local max, n, arraylen = 0, 0, 0 + for k,v in pairs (tbl) do + if k == 'n' and type(v) == 'number' then + arraylen = v + if v > max then + max = v + end + else + if type(k) ~= 'number' or k < 1 or floor(k) ~= k then + return false + end + if k > max then + max = k + end + n = n + 1 + end + end + if max > 10 and max > arraylen and max > n * 2 then + return false -- don't create an array with too many holes + end + return true, max +end + +local escapecodes = { + ["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f", + ["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t" +} + +local function escapeutf8 (uchar) + local value = escapecodes[uchar] + if value then + return value + end + local a, b, c, d = strbyte (uchar, 1, 4) + a, b, c, d = a or 0, b or 0, c or 0, d or 0 + if a <= 0x7f then + value = a + elseif 0xc0 <= a and a <= 0xdf and b >= 0x80 then + value = (a - 0xc0) * 0x40 + b - 0x80 + elseif 0xe0 <= a and a <= 0xef and b >= 0x80 and c >= 0x80 then + value = ((a - 0xe0) * 0x40 + b - 0x80) * 0x40 + c - 0x80 + elseif 0xf0 <= a and a <= 0xf7 and b >= 0x80 and c >= 0x80 and d >= 0x80 then + value = (((a - 0xf0) * 0x40 + b - 0x80) * 0x40 + c - 0x80) * 0x40 + d - 0x80 + else + return "" + end + if value <= 0xffff then + return strformat ("\\u%.4x", value) + elseif value <= 0x10ffff then + -- encode as UTF-16 surrogate pair + value = value - 0x10000 + local highsur, lowsur = 0xD800 + floor (value/0x400), 0xDC00 + (value % 0x400) + return strformat ("\\u%.4x\\u%.4x", highsur, lowsur) + else + return "" + end +end + +local function fsub (str, pattern, repl) + -- gsub always builds a new string in a buffer, even when no match + -- exists. First using find should be more efficient when most strings + -- don't contain the pattern. + if strfind (str, pattern) then + if type(repl) == 'function' then + return gsubf(str, pattern, repl) + else + return gsub (str, pattern, repl) + end + else + return str + end +end + +local function quotestring (value) + -- based on the regexp "escapable" in https://github.com/douglascrockford/JSON-js + value = fsub (value, "[%z\1-\31\"\\\127]", escapeutf8) + if strfind (value, "[\194\216\220\225\226\239]") then + value = fsub (value, "\194[\128-\159\173]", escapeutf8) + value = fsub (value, "\216[\128-\132]", escapeutf8) + value = fsub (value, "\220\143", escapeutf8) + value = fsub (value, "\225\158[\180\181]", escapeutf8) + value = fsub (value, "\226\128[\140-\143\168-\175]", escapeutf8) + value = fsub (value, "\226\129[\160-\175]", escapeutf8) + value = fsub (value, "\239\187\191", escapeutf8) + value = fsub (value, "\239\191[\176-\191]", escapeutf8) + end + return "\"" .. value .. "\"" +end +json.quotestring = quotestring + +local function replace(str, o, n) + local i, j = strfind (str, o, 1, true) + if i then + return strsub(str, 1, i-1) .. n .. strsub(str, j+1, -1) + else + return str + end +end + +-- locale independent num2str and str2num functions +local decpoint, numfilter + +local function updatedecpoint () + decpoint = strmatch(tostring(0.5), "([^05+])") + -- build a filter that can be used to remove group separators + numfilter = "[^0-9%-%+eE" .. gsub(decpoint, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") .. "]+" +end + +updatedecpoint() + +local function num2str (num) + return replace(fsub(tostring(num), numfilter, ""), decpoint, ".") +end + +local function str2num (str) + local num = tonumber(replace(str, ".", decpoint)) + if not num then + updatedecpoint() + num = tonumber(replace(str, ".", decpoint)) + end + return num +end + +local function addnewline2 (level, buffer, buflen) + buffer[buflen+1] = "\n" + buffer[buflen+2] = strrep (" ", level) + buflen = buflen + 2 + return buflen +end + +function json.addnewline (state) + if state.indent then + state.bufferlen = addnewline2 (state.level or 0, + state.buffer, state.bufferlen or #(state.buffer)) + end +end + +local encode2 -- forward declaration + +local function addpair (key, value, prev, indent, level, buffer, buflen, tables, globalorder, state) + local kt = type (key) + if kt ~= 'string' and kt ~= 'number' then + return nil, "type '" .. kt .. "' is not supported as a key by JSON." + end + if prev then + buflen = buflen + 1 + buffer[buflen] = "," + end + if indent then + buflen = addnewline2 (level, buffer, buflen) + end + buffer[buflen+1] = quotestring (key) + buffer[buflen+2] = ":" + return encode2 (value, indent, level, buffer, buflen + 2, tables, globalorder, state) +end + +local function appendcustom(res, buffer, state) + local buflen = state.bufferlen + if type (res) == 'string' then + buflen = buflen + 1 + buffer[buflen] = res + end + return buflen +end + +local function exception(reason, value, state, buffer, buflen, defaultmessage) + defaultmessage = defaultmessage or reason + local handler = state.exception + if not handler then + return nil, defaultmessage + else + state.bufferlen = buflen + local ret, msg = handler (reason, value, state, defaultmessage) + if not ret then return nil, msg or defaultmessage end + return appendcustom(ret, buffer, state) + end +end + +function json.encodeexception(reason, value, state, defaultmessage) + return quotestring("<" .. defaultmessage .. ">") +end + +encode2 = function (value, indent, level, buffer, buflen, tables, globalorder, state) + local valtype = type (value) + local valmeta = getmetatable (value) + valmeta = type (valmeta) == 'table' and valmeta -- only tables + local valtojson = valmeta and valmeta.__tojson + if valtojson then + if tables[value] then + return exception('reference cycle', value, state, buffer, buflen) + end + tables[value] = true + state.bufferlen = buflen + local ret, msg = valtojson (value, state) + if not ret then return exception('custom encoder failed', value, state, buffer, buflen, msg) end + tables[value] = nil + buflen = appendcustom(ret, buffer, state) + elseif valtype == 'nil' then + buflen = buflen + 1 + buffer[buflen] = "null" + elseif valtype == 'number' then + local s + if value ~= value or value >= huge or -value >= huge then + -- This is the behaviour of the original JSON implementation. + s = "null" + else + s = num2str (value) + end + buflen = buflen + 1 + buffer[buflen] = s + elseif valtype == 'boolean' then + buflen = buflen + 1 + buffer[buflen] = value and "true" or "false" + elseif valtype == 'string' then + buflen = buflen + 1 + buffer[buflen] = quotestring (value) + elseif valtype == 'table' then + --[[if tables[value] then + return exception('reference cycle', value, state, buffer, buflen) + end + tables[value] = true]] + level = level + 1 + local isa, n = isarray (value) + if n == 0 and valmeta and valmeta.__jsontype == 'object' then + isa = false + end + local msg + if isa then -- JSON array + buflen = buflen + 1 + buffer[buflen] = "[" + for i = 1, n do + buflen, msg = encode2 (value[i], indent, level, buffer, buflen, tables, globalorder, state) + if not buflen then return nil, msg end + if i < n then + buflen = buflen + 1 + buffer[buflen] = "," + end + end + buflen = buflen + 1 + buffer[buflen] = "]" + else -- JSON object + local prev = false + buflen = buflen + 1 + buffer[buflen] = "{" + local order = valmeta and valmeta.__jsonorder or globalorder + if order then + local used = {} + n = #order + for i = 1, n do + local k = order[i] + local v = value[k] + if v then + used[k] = true + buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state) + prev = true -- add a seperator before the next element + end + end + for k,v in pairs (value) do + if not used[k] then + buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state) + if not buflen then return nil, msg end + prev = true -- add a seperator before the next element + end + end + else -- unordered + for k,v in pairs (value) do + buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state) + if not buflen then return nil, msg end + prev = true -- add a seperator before the next element + end + end + if indent then + buflen = addnewline2 (level - 1, buffer, buflen) + end + buflen = buflen + 1 + buffer[buflen] = "}" + end + --tables[value] = nil + else + return exception ('unsupported type', value, state, buffer, buflen, + "type '" .. valtype .. "' is not supported by JSON.") + end + return buflen +end + +function json.encode (value, state) + state = state or {} + local oldbuffer = state.buffer + local buffer = oldbuffer or {} + state.buffer = buffer + updatedecpoint() + local ret, msg = encode2 (value, state.indent, state.level or 0, + buffer, state.bufferlen or 0, state.tables or {}, state.keyorder, state) + if not ret then + error (msg, 2) + elseif oldbuffer == buffer then + state.bufferlen = ret + return true + else + state.bufferlen = nil + state.buffer = nil + return concat (buffer) + end +end + +local function loc (str, where) + local line, pos, linepos = 1, 1, 0 + while true do + pos = strfind (str, "\n", pos, true) + if pos and pos < where then + line = line + 1 + linepos = pos + pos = pos + 1 + else + break + end + end + return "line " .. line .. ", column " .. (where - linepos) +end + +local function unterminated (str, what, where) + return nil, strlen (str) + 1, "unterminated " .. what .. " at " .. loc (str, where) +end + +local function scanwhite (str, pos) + while true do + pos = strfind (str, "%S", pos) + if not pos then return nil end + local sub2 = strsub (str, pos, pos + 1) + if sub2 == "\239\187" and strsub (str, pos + 2, pos + 2) == "\191" then + -- UTF-8 Byte Order Mark + pos = pos + 3 + elseif sub2 == "//" then + pos = strfind (str, "[\n\r]", pos + 2) + if not pos then return nil end + elseif sub2 == "/*" then + pos = strfind (str, "*/", pos + 2) + if not pos then return nil end + pos = pos + 2 + else + return pos + end + end +end + +local escapechars = { + ["\""] = "\"", ["\\"] = "\\", ["/"] = "/", ["b"] = "\b", ["f"] = "\f", + ["n"] = "\n", ["r"] = "\r", ["t"] = "\t" +} + +local function unichar (value) + if value < 0 then + return nil + elseif value <= 0x007f then + return strchar (value) + elseif value <= 0x07ff then + return strchar (0xc0 + floor(value/0x40), + 0x80 + (floor(value) % 0x40)) + elseif value <= 0xffff then + return strchar (0xe0 + floor(value/0x1000), + 0x80 + (floor(value/0x40) % 0x40), + 0x80 + (floor(value) % 0x40)) + elseif value <= 0x10ffff then + return strchar (0xf0 + floor(value/0x40000), + 0x80 + (floor(value/0x1000) % 0x40), + 0x80 + (floor(value/0x40) % 0x40), + 0x80 + (floor(value) % 0x40)) + else + return nil + end +end + +local function scanstring (str, pos) + local lastpos = pos + 1 + local buffer, n = {}, 0 + while true do + local nextpos = strfind (str, "[\"\\]", lastpos) + if not nextpos then + return unterminated (str, "string", pos) + end + if nextpos > lastpos then + n = n + 1 + buffer[n] = strsub (str, lastpos, nextpos - 1) + end + if strsub (str, nextpos, nextpos) == "\"" then + lastpos = nextpos + 1 + break + else + local escchar = strsub (str, nextpos + 1, nextpos + 1) + local value + if escchar == "u" then + value = tonumber (strsub (str, nextpos + 2, nextpos + 5), 16) + if value then + local value2 + if 0xD800 <= value and value <= 0xDBff then + -- we have the high surrogate of UTF-16. Check if there is a + -- low surrogate escaped nearby to combine them. + if strsub (str, nextpos + 6, nextpos + 7) == "\\u" then + value2 = tonumber (strsub (str, nextpos + 8, nextpos + 11), 16) + if value2 and 0xDC00 <= value2 and value2 <= 0xDFFF then + value = (value - 0xD800) * 0x400 + (value2 - 0xDC00) + 0x10000 + else + value2 = nil -- in case it was out of range for a low surrogate + end + end + end + value = value and unichar (value) + if value then + if value2 then + lastpos = nextpos + 12 + else + lastpos = nextpos + 6 + end + end + end + end + if not value then + value = escapechars[escchar] or escchar + lastpos = nextpos + 2 + end + n = n + 1 + buffer[n] = value + end + end + if n == 1 then + return buffer[1], lastpos + elseif n > 1 then + return concat (buffer), lastpos + else + return "", lastpos + end +end + +local scanvalue -- forward declaration + +local function scantable (what, closechar, str, startpos, nullval, objectmeta, arraymeta) + local len = strlen (str) + local tbl, n = {}, 0 + local pos = startpos + 1 + if what == 'object' then + setmetatable (tbl, objectmeta) + else + setmetatable (tbl, arraymeta) + end + while true do + pos = scanwhite (str, pos) + if not pos then return unterminated (str, what, startpos) end + local char = strsub (str, pos, pos) + if char == closechar then + return tbl, pos + 1 + end + local val1, err + val1, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta) + if err then return nil, pos, err end + pos = scanwhite (str, pos) + if not pos then return unterminated (str, what, startpos) end + char = strsub (str, pos, pos) + if char == ":" then + if val1 == nil then + return nil, pos, "cannot use nil as table index (at " .. loc (str, pos) .. ")" + end + pos = scanwhite (str, pos + 1) + if not pos then return unterminated (str, what, startpos) end + local val2 + val2, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta) + if err then return nil, pos, err end + tbl[val1] = val2 + pos = scanwhite (str, pos) + if not pos then return unterminated (str, what, startpos) end + char = strsub (str, pos, pos) + else + n = n + 1 + tbl[n] = val1 + end + if char == "," then + pos = pos + 1 + end + end +end + +local level = 0 + +scanvalue = function (str, pos, nullval, objectmeta, arraymeta) + pos = pos or 1 + pos = scanwhite (str, pos) + if not pos then + return nil, strlen (str) + 1, "no valid JSON value (reached the end)" + end + local char = strsub (str, pos, pos) + if char == "{" then + level = level + 1 + + return scantable ('object', "}", str, pos, nullval, objectmeta, arraymeta) + elseif char == "[" then + return scantable ('array', "]", str, pos, nullval, objectmeta, arraymeta) + elseif char == "\"" then + return scanstring (str, pos) + else + local pstart, pend = strfind (str, "^%-?[%d%.]+[eE]?[%+%-]?%d*", pos) + if pstart then + local number = str2num (strsub (str, pstart, pend)) + if type(number) == 'number' then -- FIXME: change to 'if number then' when neolua bug is fixed + return number, pend + 1 + end + end + pstart, pend = strfind (str, "^%a%w*", pos) + if pstart then + local name = strsub (str, pstart, pend) + if name == "true" then + return true, pend + 1 + elseif name == "false" then + return false, pend + 1 + elseif name == "null" then + return nullval, pend + 1 + end + end + return nil, pos, "no valid JSON value at " .. loc (str, pos) + end +end + +function json.decode (str, pos, nullval, ...) + local objectmeta, arraymeta = {__jsontype = 'object'}, {__jsontype = 'array'} + return scanvalue (str, pos, nullval, objectmeta, arraymeta) +end + +function json.use_lpeg () + local g = require ("lpeg") + + if g.version() == "0.11" then + error "due to a bug in LPeg 0.11, it cannot be used for JSON matching" + end + + local pegmatch = g.match + local P, S, R = g.P, g.S, g.R + + local function ErrorCall (str, pos, msg, state) + if not state.msg then + state.msg = msg .. " at " .. loc (str, pos) + state.pos = pos + end + return false + end + + local function Err (msg) + return g.Cmt (g.Cc (msg) * g.Carg (2), ErrorCall) + end + + local SingleLineComment = P"//" * (1 - S"\n\r")^0 + local MultiLineComment = P"/*" * (1 - P"*/")^0 * P"*/" + local Space = (S" \n\r\t" + P"\239\187\191" + SingleLineComment + MultiLineComment)^0 + + local PlainChar = 1 - S"\"\\\n\r" + local EscapeSequence = (P"\\" * g.C (S"\"\\/bfnrt" + Err "unsupported escape sequence")) / escapechars + local HexDigit = R("09", "af", "AF") + local function UTF16Surrogate (match, pos, high, low) + high, low = tonumber (high, 16), tonumber (low, 16) + if 0xD800 <= high and high <= 0xDBff and 0xDC00 <= low and low <= 0xDFFF then + return true, unichar ((high - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000) + else + return false + end + end + local function UTF16BMP (hex) + return unichar (tonumber (hex, 16)) + end + local U16Sequence = (P"\\u" * g.C (HexDigit * HexDigit * HexDigit * HexDigit)) + local UnicodeEscape = g.Cmt (U16Sequence * U16Sequence, UTF16Surrogate) + U16Sequence/UTF16BMP + local Char = UnicodeEscape + EscapeSequence + PlainChar + local String = P"\"" * g.Cs (Char ^ 0) * (P"\"" + Err "unterminated string") + local Integer = P"-"^(-1) * (P"0" + (R"19" * R"09"^0)) + local Fractal = P"." * R"09"^0 + local Exponent = (S"eE") * (S"+-")^(-1) * R"09"^1 + local Number = (Integer * Fractal^(-1) * Exponent^(-1))/str2num + local Constant = P"true" * g.Cc (true) + P"false" * g.Cc (false) + P"null" * g.Carg (1) + local SimpleValue = Number + String + Constant + local ArrayContent, ObjectContent + + -- The functions parsearray and parseobject parse only a single value/pair + -- at a time and store them directly to avoid hitting the LPeg limits. + local function parsearray (str, pos, nullval, state) + local obj, cont + local npos + local t, nt = {}, 0 + repeat + obj, cont, npos = pegmatch (ArrayContent, str, pos, nullval, state) + if not npos then break end + pos = npos + nt = nt + 1 + t[nt] = obj + until cont == 'last' + return pos, setmetatable (t, state.arraymeta) + end + + local function parseobject (str, pos, nullval, state) + local obj, key, cont + local npos + local t = {} + repeat + key, obj, cont, npos = pegmatch (ObjectContent, str, pos, nullval, state) + if not npos then break end + pos = npos + t[key] = obj + until cont == 'last' + return pos, setmetatable (t, state.objectmeta) + end + + local Array = P"[" * g.Cmt (g.Carg(1) * g.Carg(2), parsearray) * Space * (P"]" + Err "']' expected") + local Object = P"{" * g.Cmt (g.Carg(1) * g.Carg(2), parseobject) * Space * (P"}" + Err "'}' expected") + local Value = Space * (Array + Object + SimpleValue) + local ExpectedValue = Value + Space * Err "value expected" + ArrayContent = Value * Space * (P"," * g.Cc'cont' + g.Cc'last') * g.Cp() + local Pair = g.Cg (Space * String * Space * (P":" + Err "colon expected") * ExpectedValue) + ObjectContent = Pair * Space * (P"," * g.Cc'cont' + g.Cc'last') * g.Cp() + local DecodeValue = ExpectedValue * g.Cp () + + function json.decode (str, pos, nullval, ...) + local state = {} + state.objectmeta, state.arraymeta = optionalmetatables(...) + local obj, retpos = pegmatch (DecodeValue, str, pos, nullval, state) + if state.msg then + return nil, state.pos, state.msg + else + return obj, retpos + end + end + + -- use this function only once: + json.use_lpeg = function () return json end + + json.using_lpeg = true + + return json -- so you can get the module using json = require "dkjson".use_lpeg() +end + +return json diff --git a/system/resource_init.lua b/system/resource_init.lua new file mode 100644 index 0000000..349ff87 --- /dev/null +++ b/system/resource_init.lua @@ -0,0 +1,289 @@ +-- local resource init stuff (similar to client resource_init) +RegisterInitHandler(function(initScript, isPreParse) + local env = { + _VERSION = _VERSION, + assert = assert, + error = error, + getmetatable = getmetatable, + ipairs = ipairs, + next = next, + pairs = pairs, + pcall = pcall, + print = print, + rawequal = rawequal, + rawget = rawget, + rawlen = rawlen, + rawset = rawset, + select = select, + setmetatable = setmetatable, + tonumber = tonumber, + tostring = tostring, + type = type, + xpcall = xpcall, + bit32 = { + arshift = bit32.arshift, + band = bit32.band, + bnot = bit32.bnot, + bor = bit32.bor, + btest = bit32.btest, + bxor = bit32.bxor, + extract = bit32.extract, + lrotate = bit32.lrotate, + lshift = bit32.lshift, + replace = bit32.replace, + rrotate = bit32.rrotate, + rshift = bit32.rshift + }, + coroutine = { + create = coroutine.create, + resume = coroutine.resume, + running = coroutine.running, + status = coroutine.status, + wrap = coroutine.wrap, + yield = coroutine.yield + }, + math = { + abs = math.abs, + acos = math.acos, + asin = math.asin, + atan = math.atan, + atan2 = math.atan2, + ceil = math.ceil, + cos = math.cos, + cosh = math.cosh, + deg = math.deg, + exp = math.exp, + floor = math.floor, + fmod = math.fmod, + frexp = math.frexp, + huge = math.huge, + ldexp = math.ldexp, + log = math.log, + max = math.max, + min = math.min, + modf = math.modf, + pi = math.pi, + pow = math.pow, + rad = math.rad, + random = math.random, + randomseed = math.randomseed, + sin = math.sin, + sinh = math.sinh, + sqrt = math.sqrt, + tan = math.tan, + tanh = math.tanh + }, + string = { + byte = string.byte, + char = string.char, + dump = string.dump, + find = string.find, + format = string.format, + gmatch = string.gmatch, + gsub = string.gsub, + len = string.len, + lower = string.lower, + match = string.match, + rep = string.rep, + reverse = string.reverse, + sub = string.sub, + upper = string.upper + }, + table = { + concat = table.concat, + insert = table.insert, + pack = table.pack, + remove = table.remove, + sort = table.sort, + unpack = table.unpack + } + } + + TriggerEvent('getResourceInitFuncs', isPreParse, function(key, cb) + env[key] = cb + end) + + local pr = print + + if not isPreParse then + env.server_scripts = function(n) + if type(n) == 'string' then + n = { n } + end + + for _, d in ipairs(n) do + AddServerScript(d) + end + end + + env.server_script = env.server_scripts + else + -- and add our native items + env.solution = function(n) + SetResourceInfo('clr_solution', n) + end + + env.description = function(n) + SetResourceInfo('description', n) + end + + env.version = function(n) + SetResourceInfo('version', n) + end + + env.client_scripts = function(n) + if type(n) == 'string' then + n = { n } + end + + for _, d in ipairs(n) do + AddClientScript(d) + end + end + + env.client_script = env.client_scripts + + env.files = function(n) + if type(n) == 'string' then + n = { n } + end + + for _, d in ipairs(n) do + AddAuxFile(d) + end + end + + env.file = env.files + + env.dependencies = function(n) + if type(n) == 'string' then + n = { n } + end + + for _, d in ipairs(n) do + AddResourceDependency(d) + end + end + + env.dependency = env.dependencies + end + + local rawget_ = rawget + local print_ = print + + local mt = { + __index = function(t, k) : object + if env[k] ~= nil then + return env[k] + end + + if rawget_(t, k) ~= nil then + return rawget_(t, k) + end + + -- as we're not going to return nothing here (to allow unknown directives to be ignored) + local f = function() + return f + end + + return function() return f end + end + } + + for k, v in pairs(env) do + if type(v) == 'function' then + env[k] = function(...) + _G.__metatable = nil + + local rv = v(...) + + _G.__metatable = mt + + return rv + end + end + end + + _G.__metatable = mt + --setmetatable(env, mt) + --setfenv(initScript, env) + + initScript() + + --env = nil + + --setfenv(initScript, _G) + + _G.__metatable = nil + +-- print('rc', findallpaths(rt)) +end) + +-- nothing, yet + +-- TODO: cleanup RPC environment stuff on coroutine end/error +local function RunRPCFunction(f, env) + local co = coroutine.create(f) + env.__co = client + + local success, err = coroutine.resume(co) + + if success then + env.SendEvents() + else + print(err) + end +end + +local rpcIdx = 1 +local rpcEnvironments = {} + +function CreateRPCContext(cl, f) + local idx = rpcIdx + rpcIdx = rpcIdx + 1 + + local key = cl .. '_' .. idx + + local env = { + getIdx = function() + return idx + end, + getSource = function() + return cl + end + } + + local lastEnv = _ENV + + setmetatable(env, {__index = _G}) + + local _ENV = env + rpcEnvironments[key] = env + + setfenv(f, env) + + local fRun = f() + + local virtenv_init = loadfile('system/virtenv_init.lua', 't', env) + virtenv_init() + + _ENV = lastEnv + + RunRPCFunction(fRun, env) +end + +RegisterServerEvent('svRpc:results') + +AddEventHandler('svRpc:results', function(results) + if not results.idx then + return + end + + local key = source .. '_' .. results.idx + + if not rpcEnvironments[key] then + return + end + + rpcEnvironments[key].HandleResults(results) +end) diff --git a/system/virtenv_init.lua b/system/virtenv_init.lua new file mode 100644 index 0000000..998a755 --- /dev/null +++ b/system/virtenv_init.lua @@ -0,0 +1,99 @@ +local execQueue = {} +local execQueueArgNum = 1 +local execResults = {} +local curRoutine + +_i = { _a = '_i' } +_f = { _a = '_f' } + +function GetResult(argNum) + if not execResults[argNum] then + if #execQueue > 0 then + execQueue.idx = getIdx() + + curRoutine = coroutine.running() + coroutine.yield() + + execQueue = {} + end + end + + local r = execResults[argNum] + execResults[argNum] = nil + + return r +end + +function HandleResults(results) + for k, v in pairs(results) do + execResults[k] = v + end + + if coroutine.status(curRoutine) == 'dead' then + return + end + + local success, err = coroutine.resume(curRoutine) + + if success then + SendEvents({ getSource = getSource }) + else + print(err) + end +end + +function SendEvents(env) + TriggerClientEvent('svRpc:run', getSource(), 10, execQueue) +end + +function CallNative(hash, ...) + local arguments = {} + local returns = {} + + local arg = {...} + + for i, v in ipairs(arg) do + local a = v + + if type(v) == 'table' then + if v._a then + if v._a == '_i' or v._a == '_f' then + a._i = execQueueArgNum + execQueueArgNum = execQueueArgNum + 1 + + local fakeRetVal = { + _a = '_z', + _i = a._i + } + + -- this will only work in Lua 5.2+; as metamethod yielding got added there + setmetatable(fakeRetVal, { + __call = function() + if not fakeRetVal._value then + fakeRetVal._value = GetResult(fakeRetVal._i) + end + + return fakeRetVal._value + end + }) + + table.insert(returns, fakeRetVal) + end + end + end + + table.insert(arguments, a) + end + + table.insert(execQueue, { + h = hash, + a = arguments + }) + + return table.unpack(returns) +end + +function PrintStringWithLiteralString(...) return CallNative(0x3F89280B, ...) end +function PrintStringWithLiteralStringNow(...) return CallNative(0xCA539D6, ...) end +function GetCharCoordinates(...) return CallNative(0x2B5C06E6, ...) end +function GetPlayerChar(...) return CallNative(0x511454A9, ...) end