├── steamcmd.md5sum ├── a2oa.md5sum ├── .gitignore ├── epoch_client.md5sum ├── .gitmodules ├── script.steam ├── packages.sh ├── script.epoch ├── CONFIGURATION-dist ├── server.cfg-dist ├── README.md ├── install.sh └── writer.pl-template /steamcmd.md5sum: -------------------------------------------------------------------------------- 1 | 09e3f75c1ab5a501945c8c8b10c7f50e downloads/steamcmd_linux.tar.gz 2 | -------------------------------------------------------------------------------- /a2oa.md5sum: -------------------------------------------------------------------------------- 1 | 2c7c378c357876b2d46853ee957b8916 downloads/a2oa-server-1.63.126652.tar.bz2 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | CONFIGURATION 3 | *\#* 4 | *~ 5 | downloads/ 6 | steamcmd/ 7 | arma2-server/ 8 | -------------------------------------------------------------------------------- /epoch_client.md5sum: -------------------------------------------------------------------------------- 1 | 192dd4ef73fb6a891a1ca6f8599c94ca downloads/DayZ_Epoch_Client_1.0.5.1_Release.7z 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Dayz-Epoch-Linux-Server"] 2 | path = Dayz-Epoch-Linux-Server 3 | url = https://github.com/denisio/Dayz-Epoch-Linux-Server.git 4 | -------------------------------------------------------------------------------- /script.steam: -------------------------------------------------------------------------------- 1 | @sSteamCmdForcePlatformType windows 2 | app_update 33900 validate 3 | app_update 33910 validate 4 | app_update 33930 validate 5 | app_update 219540 beta112555 validate 6 | quit 7 | -------------------------------------------------------------------------------- /packages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | apt-get install perl screen mysql-server mysql-client libjson-xs-perl libdbd-mysql-perl git ctorrent curl p7zip 3 | 4 | [ `uname -m` == 'x86_64' ] && apt-get install lib32stdc++6 5 | -------------------------------------------------------------------------------- /script.epoch: -------------------------------------------------------------------------------- 1 | export LD_LIBRARY_PATH=.:/usr/lib32:$LD_LIBRARY_PATH 2 | # Clear the cache to eliminate the rollback bug 3 | rm -rf cache/players/* 4 | ./epoch -server -mod="@dayz_epoch;@dayz_epoch_server;" -config="cfgdayz/server.cfg" -cfg="cfgdayz/basic.cfg" -port=2302 -beta="expansion/beta;expansion/beta/expansion" -noSound -noPause -world=Chernarus -profiles=cfgdayz -name=cfgdayz -cpucount=2 -exThreads=3 -showscripterrors -pid=2302.pid 2>&1 | ./writer.pl 5 | -------------------------------------------------------------------------------- /CONFIGURATION-dist: -------------------------------------------------------------------------------- 1 | # Edit these: 2 | 3 | STEAM_USERNAME= 4 | STEAM_PASSWORD= 5 | 6 | # Mysql administrative user for database creation 7 | MYSQL_ADMIN_USER=root 8 | MYSQL_ADMIN_PASSWORD=root 9 | 10 | # The database and user/password to be created 11 | MYSQL_EPOCH_DB=dayz_epoch 12 | MYSQL_EPOCH_USER=dayz_epoch 13 | MYSQL_EPOCH_PASSWORD=changeme 14 | 15 | # Where to compose the server 16 | SERVER_PATH=../epoch 17 | 18 | # Where to keep the downloads 19 | CACHE=./downloads 20 | 21 | # Don't edit these 22 | 23 | ARMA2_SERVER_URL=https://dl.dropboxusercontent.com/u/18463425/a2oa/a2oa-server-1.63.126652.tar.bz2 24 | STEAMCMD_URL=http://media.steampowered.com/installer/steamcmd_linux.tar.gz 25 | EPOCH_CLIENT_TARBALL=DayZ_Epoch_Client_1.0.5.1_Release.7z 26 | EPOCH_CLIENT_URL=https://s3.amazonaws.com/dayzepoch/$EPOCH_CLIENT_TARBALL?torrent 27 | -------------------------------------------------------------------------------- /server.cfg-dist: -------------------------------------------------------------------------------- 1 | hostname = "DayZ - Epoch 1.0.5.1 Chernarus Linux DS"; 2 | password = ""; 3 | passwordAdmin = "changeme"; 4 | reportingIP = "arma2oapc.master.gamespy.com"; 5 | logFile = "server.log"; 6 | 7 | motd[] = { 8 | "DayZ Epoch Mod 1.0.5.1 Linux server", 9 | "", 10 | "Admin: didnt@read.the.readme" 11 | }; 12 | 13 | motdInterval = 5; 14 | maxPlayers = 100; 15 | kickDuplicate = 1; 16 | verifySignatures = 2; 17 | equalModRequired = 0; 18 | requiredBuild = 108074; 19 | voteMissionPlayers = 1; 20 | voteThreshold = 2; 21 | disableVoN = 0; 22 | vonCodecQuality = 10; 23 | persistent = 1; 24 | timeStampFormat = "short"; 25 | BattlEye = 1; 26 | 27 | onUserConnected = ""; 28 | onUserDisconnected = ""; 29 | doubleIdDetected = ""; 30 | 31 | onUnsignedData = "kick (_this select 0)"; 32 | onHackedData = "kick (_this select 0)"; 33 | onDifferentData = ""; 34 | 35 | class Missions 36 | { 37 | class DayZ { 38 | template = dayz_epoch_11.chernarus; 39 | difficulty = "veteran"; 40 | }; 41 | }; 42 | 43 | steamport = 2300; 44 | steamqueryport = 2301; 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Automated DayZ Epoch Linux server _magic_ installer 2 | =========================================== 3 | 4 | Current Epoch version: *1.0.5.1* 5 | 6 | Current Arma 2 AO server engine: *1.63.126652* (as of 2014-09-03) 7 | 8 | Current Arma 2 AO beta data files: *1.63.122548* 9 | 10 | What is this? 11 | ------------- 12 | 13 | This is a hands-free automated installer for Dayz Epoch game 14 | server. It runs on native Linux binaries and denisio's private hive scripts 15 | based on MySQL. Wine is not required. 16 | 17 | This script is designed to spare you from a very long and annoying procedure of uploading 18 | binaries from your home Windows machine and figure out the correct order of things. 19 | 20 | Here is what it does exactly: 21 | 22 | * Downloads the game data files from Steam (this method can and should be used with 23 | all linux game servers instead of uploading the binaries manually) 24 | * Downloads the current server binary from BIS website 25 | * Downloads the necessary PBOs 26 | * Downloads the Linux hive port by denisio 27 | * Composes all of the above to create a working server environment 28 | * Throws in some patched scripts for niceness 29 | * Creates a single, default database instance and fills it with the default dump 30 | 31 | I tested it on DigitalOcean 2Gb RAM VPS with Debian 7, the whole 32 | process takes 10 minutes and results in a fully working server. 33 | 34 | What do I need? 35 | --------------- 36 | 37 | * A Linux machine with a recent (2.16+) glibc/eglibc, Debian Wheezy works if you upgrade glibc, see http://stackoverflow.com/questions/10863613/how-to-upgrade-glibc-from-version-2-13-to-2-15-on-debian 38 | * About 20Gb disk space and 2Gb RAM (the server will probably run with less; haven't tried) 39 | * A Steam account with Arma II and Arma II: AO 40 | 41 | How do I run this? 42 | ------------------ 43 | 44 | * If possible, create a separate system user, e.g. `epoch`, and perform all operations in and as it 45 | * Clone this repository with `git clone git@github.com:emestee/dayz-epoch-linux-server-magic.git`. Do not use Github's "download 46 | as zip" button; won't work. 47 | * On Debian, run `packages.sh` **as root** to install the prerequisites. Otherwise look at the content of the file 48 | and install the equivalent packages. 49 | * Copy the `CONFIGURATION-dist` file to `CONFIGURATION` and edit it. At the bare minimum insert your steam login and password, 50 | and the database passwords. 51 | * If you already have some of the files this script uses, but them in `downloads/`; the existing files will not 52 | be downloaded (but signature checked) 53 | * If you have Steam Guard enabled, at the download stage it will ask you for a guard code that you receive via the email 54 | associated with your account. **Important**: after the installation is finished, remove your login/password from this file. 55 | Never leave it on a server. 56 | * Run `./install.sh all` 57 | 58 | Occasionally Steam would barf and the download will stop. Just try again. 59 | 60 | After the whole thing finished running, hopefully without failure, you 61 | will end up with a preconfigured server in `../epoch`. 62 | 63 | It failed, what do I do? 64 | ------------------------ 65 | 66 | It's probably my fault! This script is a hack. My goal is to make the 67 | procedure painless. Let me know and I will try to help you fixing it. 68 | 69 | If there's trouble downloading you can obtain the files yourself and put them in `downloads/`. The script 70 | will not attempt to download them if they exist. 71 | 72 | If something was wrong with your configuration, adjust it and perform 73 | `./install.sh reset` and then `./install.sh all` again. **Note that this 74 | will wipe both your server directory and the database.** 75 | 76 | Try to figure out what went wrong. The script is organized into 77 | stages; reset as above, look at the `all` procedure, and execute every stage manually 78 | via `./install.sh `. 79 | 80 | What's next? 81 | ------------ 82 | 83 | * Check that the game server runs correctly by going to the server directory and running `./epoch.sh` 84 | * Log in to the game as a player, run around, kill some zombies, log off, log on again and see that everything is fine (you're 85 | spawned back where you were and your stuff isn't missing) 86 | * Configure your server (hostname, motd, slots, password, etc). At the very least change the MOTD and battleye rcon password. 87 | * Note that the current server PBO has missions in Russian because that's how denisio publishes it. The recipe how to fix it can be found on epochmod.com forums. 88 | * Run with `./restarter.pl`. Use cron to run the script periodically; this will restart the server. 89 | * After everything is in order, delete this installer, or at the very least, **remove your steam user and password from the `CONFIGURATION` file** 90 | 91 | Thanks 92 | ------ 93 | 94 | * BIS for an amazing game and the engine 95 | * Epoch & DayZ mod developers 96 | * denisio for porting the hive mechanism to Linux 97 | * DeanReid for help 98 | * rezor92 for patches 99 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | [ ! -f CONFIGURATION ] && { 4 | echo You haven\'t created the CONFIGURATION file, read the instructions you zombie! 5 | exit 1 6 | } 7 | . CONFIGURATION 8 | 9 | fail () { 10 | echo "***FAILED*** $1" 11 | exit 1 12 | } 13 | 14 | help () { 15 | echo "Syntax: ./install.sh " 16 | echo 17 | echo "Available commands are:" 18 | echo 19 | echo "* all - Download and install everything; use this, everything else is steps in case stuff breaks" 20 | echo "* dl_epoch - Download Epoch client files (torrent)" 21 | echo "* dl_steamcmd - Download and unpack SteamCmd" 22 | echo "* dl_game - Download ARMA II, ARMA II: OA and the beta packages using SteamCMD" 23 | echo "* dl_server - Download Linux server binaries" 24 | echo "* dl_tools - Download denisio's server tools" 25 | echo "* compose - Combine everything into a working server directory" 26 | echo "* patch_writer - Update writer.pl with database credentials" 27 | echo "* sql - Initialize the SQL database" 28 | echo "* clean - Remove unneeded garbage from the server directory" 29 | echo "* reset - Start from scratch (destroys the server directory and the SQL database)" 30 | echo 31 | echo "Server will be installed into ${SERVER_PATH}" 32 | } 33 | 34 | all () { 35 | [ -d "$SERVER_PATH" ] && fail "Safety: server directory $SERVER_PATH already exists, this procedure will destroy it; exiting." 36 | 37 | mkdir -p $CACHE $SERVER_PATH 38 | 39 | echo "Installing Steam" 40 | dl_steamcmd 41 | echo "Downloading and installing the game - long" 42 | dl_game 43 | echo "Downloading the server binaries" 44 | dl_server 45 | echo "Downloading the server tools" 46 | dl_tools 47 | echo "Downloading and installing the Epoch client file via bittorrent - long" 48 | dl_epoch 49 | echo "Here be dragons! Composing the rest." 50 | compose 51 | echo "Initializing the database" 52 | sql 53 | echo "Cleanup" 54 | clean 55 | echo "All done!" 56 | } 57 | 58 | dl_steamcmd () { 59 | [ ! -f "${CACHE}/steamcmd_linux.tar.gz" ] && { 60 | curl -s -o $CACHE/steamcmd_linux.tar.gz $STEAMCMD_URL || fail "Unable to download SteamCmd" 61 | } || echo "${CACHE}/steamcmd_linux.tar.gz already exists, skipping download" 62 | 63 | md5sum -c steamcmd.md5sum || fail "$CACHE/steamcmd_linux.tar.gz is corrupt" 64 | rm -rf steamcmd 65 | mkdir steamcmd -p 66 | tar xzf $CACHE/steamcmd_linux.tar.gz -C steamcmd || fail "Unable to unpack SteamCmd" 67 | } 68 | 69 | dl_game () { 70 | steamcmd/steamcmd.sh +login $STEAM_USERNAME $STEAM_PASSWORD +force_install_dir ../${SERVER_PATH} +runscript ../script.steam || fail "SteamCmd game installation failed" 71 | } 72 | 73 | dl_server () { 74 | local a2oa_tarball=$(basename $ARMA2_SERVER_URL) 75 | [ ! -f "${CACHE}/${a2oa_tarball}" ] && { 76 | curl -s -o $CACHE/${a2oa_tarball} $ARMA2_SERVER_URL || fail "Unable to download the server tarball from $ARMA2_SERVER_URL" 77 | } || echo "${a2oa_tarball} exists, skipping download" 78 | 79 | md5sum -c a2oa.md5sum || fail "${a2oa_tarball} is corrupt" 80 | 81 | rm -rf arma2-server 82 | mkdir -p arma2-server 83 | tar xjf $CACHE/${a2oa_tarball} -C arma2-server || fail "Unable to extract the server tarball" 84 | } 85 | 86 | dl_tools () { 87 | git submodule init 88 | git submodule update || exit "Failed to update git submodules" 89 | } 90 | 91 | dl_epoch () { 92 | [ ! -f "${CACHE}/${EPOCH_CLIENT_TARBALL}" ] && { 93 | curl -s -o $CACHE/${EPOCH_CLIENT_TARBALL}.torrent $EPOCH_CLIENT_URL || fail "Epoch client torrent file unavailable" 94 | 95 | pushd $CACHE > /dev/null 96 | ctorrent -e 0 ${EPOCH_CLIENT_TARBALL}.torrent || fail "Unable to download the Epoch client file from bittorrent" 97 | popd > /dev/null 98 | } || echo "Epoch client file already in place, skipping bittorrent download" 99 | 100 | md5sum -c epoch_client.md5sum || fail "${EPOCH_CLIENT_TARBALL} is corrupt" 101 | } 102 | 103 | extract_epoch () { 104 | echo Installing Epoch client files 105 | 7zr x -o${SERVER_PATH} ${CACHE}/${EPOCH_CLIENT_TARBALL} > /dev/null || fail "Unable to extract ${EPOCH_CLIENT_TARBALL}, file corrupt/disk full?" 106 | } 107 | 108 | patch_writer () { 109 | # Patch writer.pl with SQL credentials 110 | cp writer.pl-template $SERVER_PATH/writer.pl 111 | sed -i "s/@@DB_NAME@@/${MYSQL_EPOCH_DB}/" $SERVER_PATH/writer.pl 112 | sed -i "s/@@DB_LOGIN@@/${MYSQL_EPOCH_USER}/" $SERVER_PATH/writer.pl 113 | sed -i "s/@@DB_PASSWD@@/${MYSQL_EPOCH_PASSWORD}/" $SERVER_PATH/writer.pl 114 | chmod +x $SERVER_PATH/writer.pl 115 | } 116 | 117 | clean () { 118 | find $SERVER_PATH -iname '*.pdf' -delete 119 | find $SERVER_PATH -iname '*.exe' -delete 120 | find $SERVER_PATH -iname '*.vdf' -delete 121 | find $SERVER_PATH -iname '*.cmd' -delete 122 | find $SERVER_PATH -iname '*.dll' -delete 123 | find $SERVER_PATH -iname '*.bat' -delete 124 | rm -rf $SERVER_PATH/dll $SERVER_PATH/besetup $SERVER_PATH/directx 125 | 126 | find $SERVER_PATH -type f -exec chmod a-x {} \; 127 | chmod u+x $SERVER_PATH/epoch $SERVER_PATH/*.pl $SERVER_PATH/*.sh 128 | } 129 | 130 | compose () { 131 | extract_epoch 132 | 133 | echo "Applying downcasing, dog bless" 134 | find $SERVER_PATH -depth -exec rename 's/(.*)\/([^\/]*)/$1\/\L$2/' {} \; 135 | 136 | echo "Installing denisio's Linux tools" 137 | cp Dayz-Epoch-Linux-Server/*.pl $SERVER_PATH 138 | cp Dayz-Epoch-Linux-Server/*.sh $SERVER_PATH 139 | cp -r Dayz-Epoch-Linux-Server/cache $SERVER_PATH 140 | cp -r Dayz-Epoch-Linux-Server/cfgdayz $SERVER_PATH 141 | cp -r Dayz-Epoch-Linux-Server/@dayz_epoch_server $SERVER_PATH 142 | cp -r Dayz-Epoch-Linux-Server/expansion $SERVER_PATH 143 | cp -r Dayz-Epoch-Linux-Server/keys $SERVER_PATH 144 | cp -r Dayz-Epoch-Linux-Server/mpmissions $SERVER_PATH 145 | 146 | echo "Patching server configuration" 147 | cp server.cfg-dist ${SERVER_PATH}/cfgdayz/server.cfg 148 | echo "Patching writer.pl" 149 | patch_writer 150 | 151 | echo "Installing server binaries" 152 | cp arma2-server/server $SERVER_PATH/epoch 153 | cp arma2-server/*.so $SERVER_PATH 154 | cp arma2-server/steam_appid.txt $SERVER_PATH 155 | 156 | cp -r arma2-server/expansion $SERVER_PATH 157 | 158 | echo "Patching epoch.sh" 159 | cp script.epoch $SERVER_PATH/epoch.sh 160 | chmod +x $SERVER_PATH/epoch.sh 161 | } 162 | 163 | sql () { 164 | mysqladmin -u${MYSQL_ADMIN_USER} -p${MYSQL_ADMIN_PASSWORD} create ${MYSQL_EPOCH_DB} || fail "Unable to create the database; invalid user/password?" 165 | mysql -u${MYSQL_ADMIN_USER} -p${MYSQL_ADMIN_PASSWORD} -e "GRANT ALL PRIVILEGES ON ${MYSQL_EPOCH_DB}.* TO '${MYSQL_EPOCH_USER}'@'localhost' IDENTIFIED BY '${MYSQL_EPOCH_PASSWORD}';" 166 | cat Dayz-Epoch-Linux-Server/database.sql Dayz-Epoch-Linux-Server/v1042update.sql Dayz-Epoch-Linux-Server/v1042a_update.sql Dayz-Epoch-Linux-Server/v1051update.sql | mysql -u${MYSQL_EPOCH_USER} -p${MYSQL_EPOCH_PASSWORD} ${MYSQL_EPOCH_DB} 167 | } 168 | 169 | reset () { 170 | rm -rf $SERVER_PATH 171 | yes|mysqladmin -u${MYSQL_ADMIN_USER} -p${MYSQL_ADMIN_PASSWORD} drop ${MYSQL_EPOCH_DB} > /dev/null 172 | 173 | } 174 | [ "$1" = "" ] && { 175 | help 176 | exit 177 | } 178 | 179 | $* 180 | echo End. 181 | exit 0 182 | -------------------------------------------------------------------------------- /writer.pl-template: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | # 3 | # Copyright 2014 by Denis Erygin, 4 | # denis.erygin@gmail.com 5 | # 6 | 7 | use JSON::XS; 8 | use DBI; 9 | use warnings; 10 | use strict; 11 | 12 | use constant { 13 | INSTANCE => 11, # Chernarus instance 14 | DB_NAME => '@@DB_NAME@@', # Set database name 15 | DB_LOGIN => '@@DB_LOGIN@@', # Set database login 16 | DB_PASSWD => '@@DB_PASSWD@@', # Set database password 17 | DB_HOST => 'localhost', # Set database host 18 | DB_PORT => 3306, # Set database port (default 3306) 19 | 20 | CACHE_DIR => $ENV{'PWD'}.'/cache/', 21 | # Start inventory of player 22 | INVENTORY => '[["ItemFlashlight","ItemMap","ItemGPS","MeleeCrowbar"],["ItemBandage","ItemPainkiller","ItemSodaPepsi","ItemSodaCoke","FoodbeefCooked"]]', 23 | BACKPACK => '["DZ_Patrol_Pack_EP1",[],[]]', 24 | MODEL => '"Survivor2_DZ"' 25 | }; 26 | 27 | my %cid_inv = (); 28 | my %obj_inv = (); 29 | 30 | my $myPlayerCounter = 1; 31 | 32 | my %FN_IPC = ( 33 | 11 => \&h_player_counter, 34 | 21 => \&h_player_split_inventory, 35 | 22 => \&h_player_split_backpack, 36 | 31 => \&h_object_change_lock_code, 37 | 33 => \&h_object_split_inventory, 38 | 39 => \&h_object_uid_split_inventory, 39 | 101 => \&h_load_player, 40 | 103 => \&h_log_login, 41 | 201 => \&h_player_update, 42 | 202 => \&h_player_death, 43 | 204 => \&h_player_disconnect, 44 | 303 => \&h_object_update_inventory, 45 | 304 => \&h_object_delete, 46 | 305 => \&h_vehicle_moved, 47 | 306 => \&h_vehicle_damaged, 48 | 308 => \&h_object_publish, 49 | 309 => \&h_object_uid_update_inventory, 50 | 310 => \&h_object_uid_delete, 51 | 396 => \&h_object_reset_damage, 52 | 397 => \&h_object_uid_reset_damage, 53 | 398 => \&h_trade_object, 54 | ); 55 | 56 | my $dbh = connect_to_db(); 57 | my $coder = JSON::XS->new->ascii(1)->shrink(1)->allow_nonref(1)->max_depth(5)->max_size(1048576); 58 | 59 | mkdir (CACHE_DIR) unless (-d CACHE_DIR); 60 | 61 | init_traders (); 62 | init_objects_id (); 63 | init_objects (); 64 | init_default_player (); 65 | init_default_character (); 66 | update_players_cache (); 67 | 68 | open (LOG, ">>dump.log") or die $!; 69 | while () { 70 | next if ($_ =~ m/Duplicate magazine/ || $_ =~ m/arrived from nonowner/); 71 | print STDERR $_; 72 | print LOG $_; 73 | 74 | if ($_ =~ m/CHILD:/) { 75 | chop; 76 | my ($pref, $data) = split /CHILD:/ => $_; 77 | my @arr = split /:/ => $data; 78 | my $cmd = $arr[0]; 79 | next unless $cmd; 80 | 81 | my $fn = $FN_IPC{$cmd}; 82 | $fn->(\@arr) if $fn; 83 | } else { 84 | init_login_uid ($_); 85 | } 86 | } 87 | close(LOG); 88 | 89 | $dbh->disconnect; 90 | exit; 91 | 92 | #--------------------------------------------------------------------------- 93 | sub connect_to_db { 94 | my $dbh = DBI->connect('dbi:mysql:'.DB_NAME.':'.DB_HOST.':'.DB_PORT, DB_LOGIN, DB_PASSWD, 95 | {'PrintError' => 1, 'RaiseError' => 1, 'AutoCommit' => 1}) 96 | or die "Can't connect to mysql: $!"; 97 | $dbh->{'mysql_auto_reconnect'} = 1; 98 | return $dbh; 99 | } 100 | 101 | sub parse_json { 102 | my $str = shift; 103 | return unless $str; 104 | 105 | my $data; 106 | eval { $data = $coder->decode ($str); }; 107 | return if $@; 108 | return $data; 109 | } 110 | 111 | sub init_traders { 112 | h_load_trader_details(); 113 | } 114 | 115 | sub init_objects_id { 116 | h_load_objects_id(); 117 | } 118 | 119 | sub init_objects { 120 | h_stream_objects(); 121 | } 122 | 123 | sub init_default_player { 124 | my $file = CACHE_DIR.'players/default.sqf'; 125 | open (OUT, ">$file") or die "Can't open '$file'"; 126 | print OUT '["PASS",false,"1",[],'.INVENTORY.','.BACKPACK.',[0,0,0],'.MODEL.',0.96]'; 127 | close (OUT); 128 | } 129 | 130 | sub init_default_character { 131 | my $file = CACHE_DIR.'players/default-char.sqf'; 132 | open (OUT, ">$file") or die "Can't open '$file'"; 133 | print OUT '["PASS",[],[0,0,0,0],[],[],2500,"1"]'; 134 | close (OUT); 135 | } 136 | 137 | sub update_players_cache { 138 | my $sql = 'SELECT PlayerUID FROM Player_DATA'; 139 | my $sth = $dbh->prepare ($sql); 140 | my $res = $sth->execute (); 141 | return unless $res; 142 | 143 | my $PLAYERS_DIR = CACHE_DIR.'players/'.$myPlayerCounter; 144 | mkdir ($PLAYERS_DIR) unless (-d $PLAYERS_DIR); 145 | 146 | my @uids = (); 147 | while (my ($playerId) = $sth->fetchrow_array) { 148 | next unless $playerId; 149 | my $file = $PLAYERS_DIR.'/'.lc($playerId).'.sqf'; 150 | next if (-f $file); 151 | 152 | push @uids, $playerId; 153 | } 154 | $sth->finish; 155 | return unless @uids; 156 | 157 | for my $playerId (@uids) { 158 | update_player_cache ($playerId); 159 | } 160 | } 161 | 162 | sub get_playerId_by_characterId { 163 | my $characterId = shift; 164 | return unless $characterId; 165 | 166 | my $sql = 'SELECT PlayerUID FROM Character_DATA WHERE CharacterID=?'; 167 | my $sth = $dbh->prepare ($sql); 168 | my $res = $sth->execute ($characterId); 169 | return unless $res; 170 | 171 | my ($playerId) = $sth->fetchrow_array; 172 | $sth->finish; 173 | 174 | return $playerId; 175 | } 176 | 177 | sub update_player_cache { 178 | my $playerId = shift; 179 | return unless $playerId; 180 | 181 | my $sql = "SELECT CharacterID, Worldspace, Inventory, Backpack, 182 | TIMESTAMPDIFF(MINUTE, Datestamp, LastLogin) as SurvivalTime, 183 | TIMESTAMPDIFF(MINUTE, LastAte, NOW()) as MinsLastAte, 184 | TIMESTAMPDIFF(MINUTE, LastDrank, NOW()) as MinsLastDrank, 185 | Model, Humanity, KillsZ, HeadshotsZ, KillsH, KillsB, CurrentState, Medical 186 | FROM Character_DATA 187 | WHERE PlayerUID=? AND Alive = 1 AND InstanceID=? ORDER BY CharacterID DESC LIMIT 1"; 188 | my $sth = $dbh->prepare ($sql); 189 | my $res = $sth->execute ($playerId, INSTANCE); 190 | #return unless $res; 191 | 192 | my ($characterId, $worldSpace, $inventory, $backpack, $survivalTime, $minsLastAte, $minsLastDrank, $model, 193 | $humanity, $killsZ, $headshotsZ, $killsH, $killsB, $currentState, $medical) = $sth->fetchrow_array; 194 | $sth->finish; 195 | return unless (defined $characterId); 196 | 197 | my $survival = '[0,0,0]'; 198 | $survival = '['.$survivalTime.','.$minsLastAte.','.$minsLastDrank.']' if (defined $survivalTime && 199 | defined $minsLastAte && 200 | defined $minsLastDrank); 201 | my $PLAYERS_DIR = CACHE_DIR.'players/'.$myPlayerCounter; 202 | mkdir ($PLAYERS_DIR) unless (-d $PLAYERS_DIR); 203 | 204 | my $file = $PLAYERS_DIR.'/'.lc($playerId).'.sqf'; 205 | open (OUT, ">$file") or print STDERR $!; 206 | print OUT '["PASS",false,"'.$characterId.'",'.$worldSpace.','.$inventory.','.$backpack.','.$survival.','.$model.',0.96]'; 207 | close (OUT); 208 | 209 | my $stats = '[0,0,0,0]'; 210 | $stats = '['.$killsZ.','.$headshotsZ.','.$killsH.','.$killsB.']' if (defined $killsZ && defined $headshotsZ && defined $killsH && defined $killsB); 211 | 212 | $file = $PLAYERS_DIR.'/'.lc($playerId).'-char.sqf'; 213 | open (OUT, ">$file") or print STDERR $!; 214 | print OUT '["PASS",'.$medical.','.$stats.','.$currentState.','.$worldSpace.','.$humanity.',"'.$characterId.'"]'; 215 | close (OUT); 216 | 217 | return $characterId; 218 | } 219 | 220 | sub init_login_uid { 221 | my $dump = shift; 222 | return unless $dump; 223 | 224 | return unless ($dump =~ m/\sconnected/); 225 | return if ($dump =~ m/BattlEye Server: Player/); 226 | 227 | my ($p, $str) = split (/Player\s/, $dump); 228 | return unless (defined $str); 229 | 230 | my ($name, $uid) = split (/\sconnected\s/, $str); 231 | return unless ($name && $uid); 232 | 233 | #if ($uid =~ m/(\d+)/) { 234 | if ($uid =~ m/=(\w+)/) { 235 | $uid = $1; 236 | } 237 | return unless $uid; 238 | print STDERR "$uid => $name\n"; 239 | 240 | #my $PLAYERS_DIR = CACHE_DIR.'players/'.$myPlayerCounter; 241 | #my $file = $PLAYERS_DIR.'/'.$uid.'.sqf'; 242 | #return if (-e $file); 243 | 244 | h_load_player ([101, $uid, INSTANCE, $name]); 245 | } 246 | 247 | # 11 248 | sub h_player_counter { 249 | my $p = shift; 250 | return unless ($p && ref($p) eq 'ARRAY'); 251 | my ($cmd, $counter) = @$p; 252 | return unless ($counter && int($counter) > 0); 253 | $counter = int($counter); 254 | 255 | my $old = CACHE_DIR.'players/'.$myPlayerCounter; 256 | my $new = CACHE_DIR.'players/'.$counter; 257 | if (-d $old) { 258 | rename ($old, $new); 259 | } else { 260 | print STDERR "Error h_player_counter($counter): old '$myPlayerCounter' not found!\n"; 261 | mkdir ($new) or print STDERR $!,"\n"; 262 | } 263 | 264 | $myPlayerCounter = $counter; 265 | } 266 | 267 | # 21 268 | sub h_player_split_inventory { 269 | my $p = shift; 270 | return unless ($p && ref($p) eq 'ARRAY'); 271 | my ($cmd, $cid, $idx, $data) = @$p; 272 | unless ($cid && defined $idx && defined $data) { 273 | print STDERR "Error h_player_split_inventory(): characterId or idx or data undefined!\n"; 274 | return; 275 | } 276 | 277 | unless ( parse_json ($data) ) { 278 | print STDERR "Error h_player_split_inventory(): data invalid json!\n"; 279 | $cid_inv{$cid} = undef; 280 | return; 281 | } 282 | 283 | # Save weapons 284 | if ($idx == 0) { 285 | $cid_inv{$cid} = $data; 286 | return; 287 | } 288 | # Save weapons + magazines 289 | if ($idx == 1 && $cid_inv{$cid}) { 290 | my $inv = '['.$cid_inv{$cid}.','.$data.']'; 291 | $cid_inv{$cid} = undef; 292 | print STDERR "Size plr inv: ".length($inv)."\n"; 293 | 294 | unless ( parse_json($inv) ) { 295 | print STDERR "Error h_player_split_inventory(): inventory invalid json!\n"; 296 | return; 297 | } 298 | 299 | my $sth = $dbh->prepare ('UPDATE Character_DATA SET Inventory=? WHERE CharacterID=?'); 300 | my $res = $sth->execute ($cid, $inv); 301 | return $res; 302 | } 303 | } 304 | 305 | # 22 306 | sub h_player_split_backpack { 307 | my $p = shift; 308 | return unless ($p && ref($p) eq 'ARRAY'); 309 | my ($cmd, $cid, $data) = @$p; 310 | unless ($cid && defined $data) { 311 | print STDERR "Error h_player_split_backpack(): characterId or data undefined!\n"; 312 | return; 313 | } 314 | 315 | unless ( parse_json ($data) ) { 316 | print STDERR "Error h_player_split_backpack(): backpack invalid json!\n"; 317 | return; 318 | } 319 | return if ($data eq '[]'); 320 | 321 | my $sth = $dbh->prepare ('UPDATE Character_DATA SET Backpack=? WHERE CharacterID=?'); 322 | my $res = $sth->execute ($cid, $data); 323 | return $res; 324 | } 325 | 326 | # 31 327 | sub h_object_change_lock_code { 328 | my $p = shift; 329 | return unless ($p && ref($p) eq 'ARRAY'); 330 | my ($cmd, $oid, $code) = @$p; 331 | unless ($oid && $code) { 332 | print STDERR "Error h_object_change_lock_code(): objectId or code undefined!\n"; 333 | return; 334 | } 335 | $oid =~ s/"//g; 336 | 337 | my $sth = $dbh->prepare ('UPDATE Object_DATA SET CharacterID=? WHERE ObjectID=? AND Instance=?'); 338 | my $res = $sth->execute ($code, $oid, INSTANCE); 339 | return $res; 340 | } 341 | 342 | # 33 343 | sub h_object_split_inventory { 344 | my $p = shift; 345 | return unless ($p && ref($p) eq 'ARRAY'); 346 | my ($cmd, $oid, $idx, $data) = @$p; 347 | unless ($oid && defined $idx && defined $data) { 348 | print STDERR "Error h_object_split_inventory(): objectId or idx or data undefined!\n"; 349 | return; 350 | } 351 | $oid =~ s/"//g; 352 | 353 | unless ( parse_json ($data) ) { 354 | print STDERR "Error h_object_split_inventory($oid): data invalid json!\n"; 355 | $obj_inv{$oid} = undef; 356 | return; 357 | } 358 | 359 | # Save weapons 360 | if ($idx == 0) { 361 | $obj_inv{$oid} = $data; 362 | return; 363 | } 364 | # Save weapons + magazines 365 | if ($idx == 1 && $obj_inv{$oid}) { 366 | $obj_inv{$oid} .= ','.$data; 367 | return; 368 | } 369 | # Save weapons + magazines + backpack 370 | if ($idx == 2 && $obj_inv{$oid}) { 371 | my $inv = '['.$obj_inv{$oid}.','.$data.']'; 372 | $obj_inv{$oid} = undef; 373 | print STDERR "Size obj inv: ".length($inv)."\n"; 374 | 375 | unless ( parse_json($inv) ) { 376 | print STDERR "Error h_object_split_inventory($oid): inventory invalid json!\n"; 377 | return; 378 | } 379 | 380 | my $sth = $dbh->prepare ('UPDATE Object_DATA SET Inventory=? WHERE ObjectID=? AND Instance=?'); 381 | my $res = $sth->execute ($inv, $oid, INSTANCE); 382 | return $res; 383 | } 384 | } 385 | 386 | # 39 387 | sub h_object_uid_split_inventory { 388 | my $p = shift; 389 | return unless ($p && ref($p) eq 'ARRAY'); 390 | my ($cmd, $uid, $idx, $data) = @$p; 391 | unless ($uid && defined $idx && defined $data) { 392 | print STDERR "Error h_object_uid_split_inventory(): objectUID or idx or data undefined!\n"; 393 | return; 394 | } 395 | $uid =~ s/"//g; 396 | 397 | unless ( parse_json ($data) ) { 398 | print STDERR "Error h_object_uid_split_inventory($uid): data invalid json!\n"; 399 | $obj_inv{$uid} = undef; 400 | return; 401 | } 402 | 403 | # Save weapons 404 | if ($idx == 0) { 405 | $obj_inv{$uid} = $data; 406 | return; 407 | } 408 | # Save weapons + magazines 409 | if ($idx == 1 && $obj_inv{$uid}) { 410 | $obj_inv{$uid} .= ','.$data; 411 | return; 412 | } 413 | # Save weapons + magazines + backpack 414 | if ($idx == 2 && $obj_inv{$uid}) { 415 | my $inv = '['.$obj_inv{$uid}.','.$data.']'; 416 | $obj_inv{$uid} = undef; 417 | print STDERR "Size obj uid inv: ".length($inv)."\n"; 418 | 419 | unless ( parse_json($inv) ) { 420 | print STDERR "Error h_object_uid_split_inventory($uid): inventory invalid json!\n"; 421 | return; 422 | } 423 | 424 | my $sth = $dbh->prepare ('UPDATE Object_DATA SET Inventory=? WHERE ObjectUID=? AND Instance=?'); 425 | my $res = $sth->execute ($inv, $uid, INSTANCE); 426 | return $res; 427 | } 428 | } 429 | 430 | # 101 431 | sub h_load_player { 432 | my $p = shift; 433 | return unless ($p && ref($p) eq 'ARRAY'); 434 | my ($cmd, $playerId, $serverId, $playerName) = @$p; 435 | unless ($playerId && $serverId) { 436 | print STDERR "Error h_load_player(): playerId or serverId undefined!\n"; 437 | return; 438 | } 439 | $playerId =~ s/['"]//g; 440 | $serverId ||= INSTANCE; 441 | 442 | my $PLAYERS_DIR = CACHE_DIR.'players/'.$myPlayerCounter; 443 | mkdir ($PLAYERS_DIR) unless (-d $PLAYERS_DIR); 444 | 445 | my $sql = 'SELECT PlayerName, PlayerSex FROM Player_DATA WHERE PlayerUID=?'; 446 | my $sth = $dbh->prepare ($sql); 447 | my $res = $sth->execute ($playerId); 448 | 449 | my ($name, $sex) = $sth->fetchrow_array; 450 | $sth->finish; 451 | 452 | my $newPlayer = 0; 453 | if (defined $name) { 454 | if ($playerName && $playerName ne $name) { 455 | $sql = 'UPDATE Player_DATA SET PlayerName=? WHERE PlayerUID=?'; 456 | $sth = $dbh->prepare ($sql); 457 | $res = $sth->execute ($playerName, $playerId); 458 | print STDERR "Changed name of player $playerId from '$name' to '$playerName'\n"; 459 | } 460 | } else { 461 | $newPlayer = 1; 462 | $playerName = 'Unknown' unless $playerName; 463 | 464 | $sql = 'INSERT INTO Player_DATA(PlayerUID, PlayerName) VALUES (?, ?)'; 465 | $sth = $dbh->prepare ($sql); 466 | $res = $sth->execute ($playerId, $playerName); 467 | print STDERR "Created a new player $playerId named '$playerName'\n"; 468 | } 469 | 470 | $sql = "SELECT CharacterID, Worldspace, Inventory, Backpack, 471 | TIMESTAMPDIFF(MINUTE, Datestamp, LastLogin) as SurvivalTime, 472 | TIMESTAMPDIFF(MINUTE, LastAte, NOW()) as MinsLastAte, 473 | TIMESTAMPDIFF(MINUTE, LastDrank, NOW()) as MinsLastDrank, 474 | Model, Medical, Humanity, KillsZ, HeadshotsZ, KillsH, KillsB, CurrentState 475 | FROM Character_DATA 476 | WHERE PlayerUID=? AND Alive = 1 AND InstanceID=? ORDER BY CharacterID DESC LIMIT 1"; 477 | $sth = $dbh->prepare ($sql); 478 | $res = $sth->execute ($playerId, $serverId); 479 | 480 | my ($characterId, $worldSpace, $inventory, $backpack, $survivalTime, $minsLastAte, $minsLastDrank, 481 | $model, $medical, $humanity, $killsZ, $headshotsZ, $killsH, $killsB, $currentState) = $sth->fetchrow_array; 482 | $sth->finish; 483 | 484 | $currentState = '[]' unless (defined $currentState); 485 | $humanity = 2500 unless (defined $humanity); 486 | $medical = '[]' unless (defined $medical); 487 | $worldSpace = '[]' unless (defined $worldSpace); 488 | $inventory = INVENTORY unless (defined $inventory); # '[]' 489 | $backpack = BACKPACK unless (defined $backpack); # '[]' 490 | $model = MODEL unless (defined $model); # '' 491 | 492 | my $survival = '[0,0,0]'; 493 | $survival = '['.$survivalTime.','.$minsLastAte.','.$minsLastDrank.']' if (defined $survivalTime && 494 | defined $minsLastAte && 495 | defined $minsLastDrank); 496 | my $newChar = 0; 497 | if (defined $characterId) { 498 | $sql = 'UPDATE Character_DATA SET LastLogin = CURRENT_TIMESTAMP WHERE CharacterID=?'; 499 | $sth = $dbh->prepare ($sql); 500 | $res = $sth->execute ($characterId); 501 | 502 | my $stats = '[0,0,0,0]'; 503 | $stats = '['.$killsZ.','.$headshotsZ.','.$killsH.','.$killsB.']' if (defined $killsZ && defined $headshotsZ && 504 | defined $killsH && defined $killsB); 505 | my $file = $PLAYERS_DIR.'/'.lc($playerId).'-char.sqf'; 506 | open (OUT, ">$file"); 507 | print OUT '["PASS",'.$medical.','.$stats.','.$currentState.','.$worldSpace.','.$humanity.',"'.$characterId.'"]'; 508 | close (OUT); 509 | } else { 510 | $newChar = 1; 511 | 512 | $sql = "SELECT Generation, Humanity, Model FROM Character_DATA 513 | WHERE PlayerUID=? AND Alive = 0 AND InstanceID=? ORDER BY CharacterID DESC LIMIT 1"; 514 | $sth = $dbh->prepare ($sql); 515 | $res = $sth->execute ($playerId, $serverId); 516 | 517 | my ($generation, $humanity, $cmodel) = $sth->fetchrow_array; 518 | $sth->finish; 519 | 520 | if (defined $generation) { 521 | $generation++; 522 | } else { 523 | $generation = 1; 524 | } 525 | 526 | $humanity = 2500 unless (defined $humanity); 527 | $model = $cmodel if (defined $cmodel); 528 | 529 | $sql = "INSERT INTO Character_DATA(PlayerUID, InstanceID, Worldspace, Inventory, Backpack, Medical, 530 | Generation, Datestamp, LastLogin, LastAte, LastDrank, Humanity) 531 | VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, ?)"; 532 | $sth = $dbh->prepare ($sql); 533 | $res = $sth->execute ($playerId, $serverId, $worldSpace, $inventory, $backpack, $medical, $generation, $humanity); 534 | 535 | $sql = 'SELECT CharacterID FROM Character_DATA WHERE PlayerUID=? AND Alive = 1 AND InstanceID=? ORDER BY CharacterID DESC LIMIT 1'; 536 | $sth = $dbh->prepare ($sql); 537 | $res = $sth->execute ($playerId, $serverId); 538 | 539 | ($characterId) = $sth->fetchrow_array; 540 | $sth->finish; 541 | 542 | $playerName = 'Unknown' unless $playerName; 543 | 544 | if (defined $characterId) { 545 | print STDERR "Created a new character $characterId for player '$playerName' ($playerId)\n"; 546 | my $file = $PLAYERS_DIR.'/'.lc($playerId).'-char.sqf'; 547 | open (OUT, ">$file"); 548 | print OUT '["PASS",'.$medical.',[0,0,0,0],[],'.$worldSpace.','.$humanity.',"'.$characterId.'"]'; 549 | close (OUT); 550 | } else { 551 | print STDERR "Can't found new character for player '$playerName' ($playerId)\n"; 552 | } 553 | } 554 | 555 | my $file = $PLAYERS_DIR.'/'.lc($playerId).'.sqf'; 556 | open (OUT, ">$file"); 557 | print OUT '["PASS",false,"'.$characterId.'",'.$worldSpace.','.$inventory.','.$backpack.','.$survival.','.$model.',0.96]'; 558 | close (OUT); 559 | 560 | print STDERR "Save player: $file\n"; 561 | return $res; 562 | } 563 | 564 | # 102 565 | sub h_load_character { 566 | my $p = shift; 567 | return unless ($p && ref($p) eq 'ARRAY'); 568 | my ($cmd, $characterId, $playerId) = @$p; 569 | unless ($characterId) { 570 | print STDERR "Error h_load_character(): characterId undefined!\n"; 571 | return; 572 | } 573 | $characterId =~ s/"//g; 574 | $playerId =~ s/['"]//g; 575 | 576 | if ($characterId == 1 && $playerId) { 577 | my $sql = 'SELECT CharacterID FROM Character_DATA WHERE PlayerUID=? AND Alive = 1 AND InstanceID=? ORDER BY CharacterID DESC LIMIT 1'; 578 | my $sth = $dbh->prepare ($sql); 579 | my $res = $sth->execute ($playerId, INSTANCE); 580 | return unless $res; 581 | 582 | ($characterId) = $sth->fetchrow_array; 583 | $sth->finish; 584 | } 585 | unless ($characterId) { 586 | print STDERR "Error h_load_character(): characterId undefined!\n"; 587 | return; 588 | } 589 | 590 | my $sql = "SELECT Worldspace, Medical, Generation, KillsZ, HeadshotsZ, KillsH, KillsB, CurrentState, Humanity, PlayerUID 591 | FROM Character_DATA WHERE CharacterID=?"; 592 | my $sth = $dbh->prepare ($sql); 593 | my $res = $sth->execute ($characterId); 594 | 595 | my ($worldSpace, $medical, $generation, $killsZ, $headshotsZ, $killsH, $killsB, $currentState, $humanity, $playerUID) = $sth->fetchrow_array; 596 | $sth->finish; 597 | return unless (defined $worldSpace); 598 | 599 | $playerId = $playerUID if (defined $playerUID); 600 | 601 | $worldSpace ||= '[]'; 602 | $medical ||= '[]'; 603 | $generation ||= 1; 604 | $currentState ||= '[]'; 605 | $humanity = 2500 unless (defined $humanity); 606 | 607 | my $stats = '[0,0,0,0]'; 608 | $stats = '['.$killsZ.','.$headshotsZ.','.$killsH.','.$killsB.']' if (defined $killsZ && defined $headshotsZ && 609 | defined $killsH && defined $killsB); 610 | 611 | my $file = CACHE_DIR.'players/'.$myPlayerCounter.'/'.lc($playerId).'-char.sqf'; 612 | open (OUT, ">$file"); 613 | print OUT '["PASS",'.$medical.','.$stats.','.$currentState.','.$worldSpace.','.$humanity.',"'.$characterId.'"]'; 614 | close (OUT); 615 | 616 | print STDERR "Save character: $file\n"; 617 | return $res; 618 | } 619 | 620 | # 103 621 | sub h_log_login { 622 | my $p = shift; 623 | return unless ($p && ref($p) eq 'ARRAY'); 624 | my ($cmd, $playerId, $characterId, $action) = @$p; 625 | unless ($playerId) { 626 | print STDERR "Error h_log_login(): playerId undefined!\n"; 627 | return; 628 | } 629 | $playerId =~ s/['"]//g; 630 | $characterId =~ s/"//g; 631 | 632 | my $sql = 'INSERT INTO Player_LOGIN(PlayerUID, CharacterID, Datestamp, Action) VALUES (?, ?, CURRENT_TIMESTAMP, ?)'; 633 | my $sth = $dbh->prepare ($sql); 634 | my $res = $sth->execute ($playerId, $characterId, $action); 635 | return $res; 636 | } 637 | 638 | # 201 639 | sub h_player_update { 640 | my $p = shift; 641 | return unless ($p && ref($p) eq 'ARRAY'); 642 | my ($cmd, $characterId, $worldSpace, $inventory, $backpack, $medical, $justAte, $justDrank, 643 | $killsZ, $headshotsZ, $distanceWalked, $durationLived, $currentState, $killsH, $killsB, $model, 644 | $humanity) = @$p; 645 | unless ($characterId) { 646 | print STDERR "Error h_player_update(): characterId undefined!\n"; 647 | return; 648 | } 649 | $characterId =~ s/"//g; 650 | 651 | if ($worldSpace) { 652 | my $ws = parse_json ($worldSpace); 653 | unless ($ws) { 654 | print STDERR "Error h_player_update(): worldSpace invalid json!\n"; 655 | return; 656 | } 657 | if (@$ws) { 658 | if ($ws->[1] && ref($ws->[1]) eq 'ARRAY') { 659 | my ($x, $y) = @{$ws->[1]}; 660 | if ($x && $x < -18000) { 661 | print STDERR "Error h_player_update(): worldSpace invalid!\n"; 662 | return; 663 | } 664 | } 665 | } 666 | } 667 | if ($inventory) { 668 | unless ( parse_json ($inventory) ) { 669 | print STDERR "Error h_player_update(): inventory invalid json!\n"; 670 | $inventory = undef; 671 | } 672 | } 673 | if ($backpack) { 674 | unless ( parse_json ($backpack) ) { 675 | print STDERR "Error h_player_update(): backpack invalid json!\n"; 676 | $backpack = undef; 677 | } 678 | } 679 | if ($medical) { 680 | unless ( parse_json ($medical) ) { 681 | print STDERR "Error h_player_update(): medical invalid json!\n"; 682 | $medical = undef; 683 | } 684 | } 685 | if ($currentState) { 686 | unless ( parse_json ($currentState) ) { 687 | print STDERR "Error h_player_update(): currentState invalid json!\n"; 688 | $currentState = undef; 689 | } 690 | } 691 | if ($model) { 692 | $model = '"'.$model.'"' if (length($model) > 3 && $model !~ m/"/); 693 | unless ( parse_json ($model) ) { 694 | print STDERR "Error h_player_update(): model invalid json!\n"; 695 | $model = undef; 696 | } 697 | } 698 | 699 | my $str = ''; 700 | $str .= 'Worldspace='.$dbh->quote($worldSpace).',' if ($worldSpace && $worldSpace ne '[]'); 701 | $str .= 'Inventory='.$dbh->quote($inventory).',' if ($inventory && $inventory ne INVENTORY && $inventory ne '[]'); 702 | $str .= 'Backpack='.$dbh->quote($backpack).',' if ($backpack && $backpack ne BACKPACK && $backpack ne '[]'); 703 | $str .= 'Medical='.$dbh->quote($medical).',' if ($medical && $medical ne '[]'); 704 | $str .= 'CurrentState='.$dbh->quote($currentState).',' if ($currentState && $currentState ne '[]'); 705 | $str .= 'Model='.$dbh->quote($model).',' if ($model && $model ne MODEL); 706 | 707 | $str .= 'LastAte=CURRENT_TIMESTAMP,' if ($justAte && $justAte eq 'true'); 708 | $str .= 'LastDrank=CURRENT_TIMESTAMP,' if ($justDrank && $justDrank eq 'true'); 709 | 710 | $str .= 'KillsZ=KillsZ+'.int($killsZ).',' if ($killsZ && $killsZ > 0); 711 | $str .= 'HeadshotsZ=HeadshotsZ+'.int($headshotsZ).',' if ($headshotsZ && $headshotsZ > 0); 712 | $str .= 'DistanceFoot=DistanceFoot+'.int($distanceWalked).',' if ($distanceWalked && $distanceWalked > 0); 713 | $str .= 'KillsH=KillsH+'.int($killsH).',' if ($killsH && $killsH > 0); 714 | $str .= 'KillsB=KillsB+'.int($killsB).',' if ($killsB && $killsB > 0); 715 | 716 | if ($humanity) { 717 | if ($humanity < 0) { 718 | $str .= 'Humanity=Humanity-'.int(-1*$humanity).','; 719 | } else { 720 | $str .= 'Humanity=Humanity+'.int($humanity).','; 721 | } 722 | } 723 | 724 | return unless $str; 725 | 726 | $str .= 'Duration=Duration+'.int($durationLived || 0); 727 | 728 | my $sql = 'UPDATE Character_DATA SET '; 729 | $sql .= $str; 730 | $sql .= ' WHERE CharacterID=?'; 731 | 732 | my $sth = $dbh->prepare ($sql); 733 | my $res = $sth->execute ($characterId); 734 | return $res; 735 | } 736 | 737 | # 202 738 | sub h_player_death { 739 | my $p = shift; 740 | return unless ($p && ref($p) eq 'ARRAY'); 741 | my ($cmd, $characterId, $duration) = @$p; 742 | unless ($characterId) { 743 | print STDERR "Error h_player_death(): characterId undefined!\n"; 744 | return; 745 | } 746 | $characterId =~ s/"//g; 747 | 748 | my $sql = "UPDATE Character_DATA SET Alive = 0, LastLogin = DATE_SUB(CURRENT_TIMESTAMP, INTERVAL ? MINUTE) 749 | WHERE CharacterID=? AND Alive = 1"; 750 | my $sth = $dbh->prepare ($sql); 751 | my $res = $sth->execute (int($duration), $characterId); 752 | return unless $res; 753 | 754 | # Reset profile 755 | my $playerId = get_playerId_by_characterId ($characterId); 756 | if ($playerId) { 757 | h_load_player ([101, $playerId, INSTANCE]); 758 | } else { 759 | print STDERR "Error h_player_death('$characterId'): playerId not found!\n"; 760 | } 761 | } 762 | 763 | # 204 764 | sub h_player_disconnect { 765 | my $p = shift; 766 | return unless ($p && ref($p) eq 'ARRAY'); 767 | my ($cmd, $playerId) = @$p; 768 | $playerId =~ s/[A-Z"]//g; 769 | 770 | update_player_cache ($playerId); 771 | } 772 | 773 | # 302 774 | sub h_stream_objects { 775 | # Clean objects 776 | for my $sql ('DELETE FROM Object_DATA WHERE Instance=? AND ClassName="TentStorage" AND (Inventory="[[[],[]],[[],[]],[[],[]]]" OR Damage=1)', 777 | 'DELETE FROM Object_DATA where Instance=? AND Damage > 0.9 AND ObjectID > 89') { 778 | my $sth = $dbh->prepare ($sql); 779 | $sth->execute (INSTANCE); 780 | } 781 | 782 | my $sql = "SELECT ObjectID, CharacterID, Worldspace, Inventory, Hitpoints, Fuel, Damage 783 | FROM Object_init_DATA WHERE Instance=? AND Classname IS NOT NULL ORDER BY ObjectID"; 784 | my $sth = $dbh->prepare ($sql); 785 | my $res = $sth->execute (INSTANCE); 786 | return unless $res; 787 | 788 | my %init = (); 789 | while (my ($objId, $ownerId, $worldSpace, $inventory, $hitpoints, $fuel, $damage) = $sth->fetchrow_array) { 790 | next unless $objId; 791 | 792 | $ownerId ||= 0; 793 | $worldSpace ||= '[]'; 794 | $inventory ||= '[]'; 795 | $hitpoints ||= '[]'; 796 | $fuel ||= 0; 797 | $damage ||= 0; 798 | 799 | $init{$objId} = [$ownerId, $worldSpace, $inventory, $hitpoints, $fuel, $damage]; 800 | } 801 | $sth->finish; 802 | 803 | $sql = "SELECT ObjectID, Classname, CharacterID, Worldspace, Inventory, Hitpoints, Fuel, Damage 804 | FROM Object_DATA WHERE Instance=? AND Classname IS NOT NULL ORDER BY ObjectID"; 805 | $sth = $dbh->prepare ($sql); 806 | $res = $sth->execute (INSTANCE); 807 | return unless $res; 808 | 809 | my $str = ''; 810 | my @updates = (); 811 | while (my ($objId, $className, $ownerId, $worldSpace, $inventory, $hitpoints, $fuel, $damage) = $sth->fetchrow_array) { 812 | if ( $objId && $damage && $damage > 0.7 && $init{$objId} ) { 813 | print STDERR "Respawn '$className' ($objId)\n"; 814 | ($ownerId, $worldSpace, $inventory, $hitpoints, $fuel, $damage) = @{$init{$objId}}; 815 | push @updates, $objId; 816 | } 817 | 818 | $ownerId ||= 0; 819 | $worldSpace ||= '[]'; 820 | $inventory ||= '[]'; 821 | $hitpoints ||= '[]'; 822 | $fuel ||= 0; 823 | $damage ||= 0; 824 | 825 | $fuel = sprintf ("%.3f", $fuel); 826 | $damage = sprintf ("%.3f", $damage); 827 | 828 | $str .= ',' if $str; 829 | $str .= '["OBJ","'.$objId.'","'.$className.'","'.$ownerId.'",'.$worldSpace.','.$inventory.','.$hitpoints.','.$fuel.','.$damage.']'; 830 | } 831 | $sth->finish; 832 | 833 | for my $objId (@updates) { 834 | my ($ownerId, $worldSpace, $inventory, $hitpoints, $fuel, $damage) = @{$init{$objId}}; 835 | $sql = 'UPDATE Object_DATA SET CharacterID=?, Worldspace=?, Inventory=?, Hitpoints=?, Fuel=?, Damage=? WHERE Instance=? AND ObjectID=?'; 836 | $sth = $dbh->prepare ($sql); 837 | $res = $sth->execute ($ownerId, $worldSpace, $inventory, $hitpoints, $fuel, $damage, INSTANCE, $objId); 838 | } 839 | #return unless $str; 840 | 841 | my $file = CACHE_DIR.'objects.sqf'; 842 | open (OUT, ">$file"); 843 | print OUT '['.$str.']'; 844 | close (OUT); 845 | } 846 | 847 | # 303 848 | sub h_object_update_inventory { 849 | my $p = shift; 850 | return unless ($p && ref($p) eq 'ARRAY'); 851 | my ($cmd, $objectId, $inventory) = @$p; 852 | unless ($objectId && $inventory) { 853 | print STDERR "Error h_object_update_inventory(): objectId or inventory undefined!\n"; 854 | return; 855 | } 856 | $objectId =~ s/"//g; 857 | 858 | unless ( parse_json ($inventory) ) { 859 | print STDERR "Error h_object_update_inventory($objectId): inventory invalid json!\n"; 860 | return; 861 | } 862 | 863 | my $sql = 'UPDATE Object_DATA SET Inventory=? WHERE ObjectID=? AND Instance=?'; 864 | my $sth = $dbh->prepare ($sql); 865 | my $res = $sth->execute ($inventory, $objectId, INSTANCE); 866 | return $res; 867 | } 868 | 869 | # 304 870 | sub h_object_delete { 871 | my $p = shift; 872 | return unless ($p && ref($p) eq 'ARRAY'); 873 | my ($cmd, $objectId) = @$p; 874 | unless ($objectId) { 875 | print STDERR "Error h_object_delete(): objectId undefined!\n"; 876 | return; 877 | } 878 | $objectId =~ s/"//g; 879 | 880 | my $sql = 'DELETE FROM Object_DATA WHERE ObjectID=? AND Instance=?'; 881 | my $sth = $dbh->prepare ($sql); 882 | my $res = $sth->execute ($objectId, INSTANCE); 883 | 884 | if ($res) { 885 | $sql = 'DELETE FROM Object_init_DATA WHERE ObjectID=? AND Instance=?'; 886 | $sth = $dbh->prepare ($sql); 887 | $res = $sth->execute ($objectId, INSTANCE); 888 | } 889 | return $res; 890 | } 891 | 892 | # 305 893 | sub h_vehicle_moved { 894 | my $p = shift; 895 | return unless ($p && ref($p) eq 'ARRAY'); 896 | my ($cmd, $objectId, $worldSpace, $fuel) = @$p; 897 | unless ($objectId && $worldSpace) { 898 | print STDERR "Error h_vehicle_moved(): objectId undefined!\n"; 899 | return; 900 | } 901 | $objectId =~ s/"//g; 902 | 903 | unless ( parse_json ($worldSpace) ) { 904 | print STDERR "Error h_vehicle_moved($objectId): worldSpace invalid json!\n"; 905 | return; 906 | } 907 | 908 | my $sql = 'UPDATE Object_DATA SET Worldspace=?, Fuel=? WHERE ObjectID=? AND Instance=?'; 909 | my $sth = $dbh->prepare ($sql); 910 | my $res = $sth->execute ($worldSpace, $fuel, $objectId, INSTANCE); 911 | return $res; 912 | } 913 | 914 | # 306 915 | sub h_vehicle_damaged { 916 | my $p = shift; 917 | return unless ($p && ref($p) eq 'ARRAY'); 918 | my ($cmd, $objectId, $hitPoints, $damage) = @$p; 919 | unless ($objectId && defined $hitPoints && defined $damage) { 920 | print STDERR "Error h_vehicle_damaged(): objectId undefined!\n"; 921 | return; 922 | } 923 | $objectId =~ s/"//g; 924 | 925 | if ($hitPoints) { 926 | unless ( parse_json ($hitPoints) ) { 927 | print STDERR "Error h_vehicle_damaged($objectId): hitPoints invalid json!\n"; 928 | return; 929 | } 930 | } 931 | 932 | $hitPoints ||= '[]'; 933 | $damage ||= 0; 934 | 935 | my $sql = 'UPDATE Object_DATA SET Hitpoints=?, Damage=? WHERE ObjectID=? AND Instance=?'; 936 | my $sth = $dbh->prepare ($sql); 937 | my $res = $sth->execute ($hitPoints, $damage, $objectId, INSTANCE); 938 | return $res; 939 | } 940 | 941 | # 308 942 | sub h_object_publish { 943 | my $p = shift; 944 | return unless ($p && ref($p) eq 'ARRAY'); 945 | my ($cmd, $serverId, $className, $damage, $characterId, $worldSpace, $inventory, $hitPoints, $fuel, $objectUID) = @$p; 946 | unless ($className) { 947 | print STDERR "Error h_object_publish(): className undefined!\n"; 948 | return; 949 | } 950 | $characterId =~ s/"//g; 951 | $objectUID =~ s/"//g; 952 | 953 | if ($worldSpace) { 954 | unless ( parse_json ($worldSpace) ) { 955 | print STDERR "Error h_object_publish($className): worldSpace invalid json!\n"; 956 | return; 957 | } 958 | } 959 | if ($inventory) { 960 | unless ( parse_json ($inventory) ) { 961 | print STDERR "Error h_object_publish($className): inventory invalid json!\n"; 962 | $inventory = '[]'; 963 | } 964 | } 965 | if ($hitPoints) { 966 | unless ( parse_json ($hitPoints) ) { 967 | print STDERR "Error h_object_publish($className): hitPoints invalid json!\n"; 968 | $hitPoints = '[]'; 969 | } 970 | } 971 | 972 | $serverId ||= INSTANCE; 973 | $worldSpace ||= '[]'; 974 | $inventory ||= '[]'; 975 | $hitPoints ||= '[]'; 976 | $damage ||= 0; 977 | $fuel ||= 0; 978 | 979 | my $sql = "INSERT INTO Object_DATA(ObjectUID, Instance, Classname, Damage, CharacterID, Worldspace, Inventory, 980 | Hitpoints, Fuel, Datestamp) 981 | VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)"; 982 | my $sth = $dbh->prepare ($sql); 983 | my $res = $sth->execute ($objectUID, $serverId, $className, $damage, $characterId, $worldSpace, $inventory, $hitPoints, $fuel); 984 | 985 | # Cache object ID 986 | $sql = 'SELECT ObjectID FROM Object_DATA WHERE ObjectUID=? AND Instance=?'; 987 | $sth = $dbh->prepare ($sql); 988 | $res = $sth->execute ($objectUID, $serverId); 989 | return unless $res; 990 | 991 | my ($objectId) = $sth->fetchrow_array; 992 | $sth->finish; 993 | return unless (defined $objectId); 994 | 995 | my $file = CACHE_DIR.'objects/'.$objectUID.'.sqf'; 996 | open (OUT, ">$file"); 997 | print OUT '["PASS","'.$objectId.'"]'; 998 | close (OUT); 999 | } 1000 | 1001 | # 309 1002 | sub h_object_uid_update_inventory { 1003 | my $p = shift; 1004 | return unless ($p && ref($p) eq 'ARRAY'); 1005 | my ($cmd, $objectUID, $inventory) = @$p; 1006 | unless ($objectUID && $inventory) { 1007 | print STDERR "Error h_object_uid_update_inventory(): objectUID or inventory undefined!\n"; 1008 | return; 1009 | } 1010 | $objectUID =~ s/"//g; 1011 | 1012 | unless ( parse_json ($inventory) ) { 1013 | print STDERR "Error h_object_uid_update_inventory($objectUID): inventory invalid json!\n"; 1014 | return; 1015 | } 1016 | 1017 | my $sql = 'UPDATE Object_DATA SET Inventory=? WHERE ObjectUID=? AND Instance=?'; 1018 | my $sth = $dbh->prepare ($sql); 1019 | my $res = $sth->execute ($inventory, $objectUID, INSTANCE); 1020 | return $res; 1021 | } 1022 | 1023 | # 310 1024 | sub h_object_uid_delete { 1025 | my $p = shift; 1026 | return unless ($p && ref($p) eq 'ARRAY'); 1027 | my ($cmd, $objectUID) = @$p; 1028 | unless ($objectUID) { 1029 | print STDERR "Error h_object_uid_delete(): objectUID undefined!\n"; 1030 | return; 1031 | } 1032 | $objectUID =~ s/"//g; 1033 | 1034 | my $sql = 'DELETE FROM Object_DATA WHERE ObjectUID=? AND Instance=?'; 1035 | my $sth = $dbh->prepare ($sql); 1036 | my $res = $sth->execute ($objectUID, INSTANCE); 1037 | 1038 | if ($res) { 1039 | $sql = 'DELETE FROM Object_init_DATA WHERE ObjectUID=? AND Instance=?'; 1040 | $sth = $dbh->prepare ($sql); 1041 | $res = $sth->execute ($objectUID, INSTANCE); 1042 | } 1043 | return $res; 1044 | } 1045 | 1046 | # 388 - loadObjectID 1047 | sub h_load_objects_id { 1048 | my $sql = 'SELECT ObjectID, ObjectUID FROM Object_DATA WHERE Instance=? ORDER BY ObjectUID'; 1049 | my $sth = $dbh->prepare ($sql); 1050 | my $res = $sth->execute (INSTANCE); 1051 | #return unless $res; 1052 | 1053 | while (my ($objectId, $objectUID) = $sth->fetchrow_array) { 1054 | next unless (defined $objectId); 1055 | 1056 | my $file = CACHE_DIR.'objects/'.$objectUID.'.sqf'; 1057 | open (OUT, ">$file"); 1058 | print OUT '["PASS","'.$objectId.'"]'; 1059 | close (OUT); 1060 | } 1061 | $sth->finish; 1062 | } 1063 | 1064 | # 396 1065 | sub h_object_reset_damage { 1066 | my $p = shift; 1067 | return unless ($p && ref($p) eq 'ARRAY'); 1068 | my ($cmd, $oid) = @$p; 1069 | unless ($oid) { 1070 | print STDERR "Error h_object_reset_damage(): objectId undefined!\n"; 1071 | return; 1072 | } 1073 | $oid =~ s/"//g; 1074 | 1075 | my $sth = $dbh->prepare ('UPDATE Object_DATA SET Damage=0 WHERE ObjectID=? AND Instance=?'); 1076 | my $res = $sth->execute ($oid, INSTANCE); 1077 | return $res; 1078 | } 1079 | 1080 | # 397 1081 | sub h_object_uid_reset_damage { 1082 | my $p = shift; 1083 | return unless ($p && ref($p) eq 'ARRAY'); 1084 | my ($cmd, $uid) = @$p; 1085 | unless ($uid) { 1086 | print STDERR "Error h_object_uid_reset_damage(): objectUID undefined!\n"; 1087 | return; 1088 | } 1089 | $uid =~ s/"//g; 1090 | 1091 | my $sth = $dbh->prepare ('UPDATE Object_DATA SET Damage=0 WHERE ObjectUID=? AND Instance=?'); 1092 | my $res = $sth->execute ($uid, INSTANCE); 1093 | return $res; 1094 | } 1095 | 1096 | # 398 - tradeObject 1097 | sub h_trade_object { 1098 | my $p = shift; 1099 | return unless ($p && ref($p) eq 'ARRAY'); 1100 | my ($cmd, $traderObjectId, $action) = @$p; 1101 | return unless ($traderObjectId && defined $action); 1102 | 1103 | my $sql; 1104 | if ($action == 0) { 1105 | $sql = 'UPDATE Traders_DATA SET qty = qty - 1 WHERE id=? AND qty > 0'; 1106 | } else { 1107 | $sql = 'UPDATE Traders_DATA SET qty = qty + 1 WHERE id=?'; 1108 | } 1109 | my $sth = $dbh->prepare ($sql); 1110 | my $res = $sth->execute ($traderObjectId); 1111 | return $res; 1112 | } 1113 | 1114 | # 399 - loadTraderDetails 1115 | sub h_load_trader_details { 1116 | my $sql = 'SELECT id, item, qty, buy, sell, `order`, tid, afile FROM Traders_DATA'; 1117 | my $sth = $dbh->prepare ($sql); 1118 | my $res = $sth->execute (); 1119 | return unless $res; 1120 | 1121 | my %tids = (); 1122 | my $charecterId = 0; 1123 | while (my ($id, $item, $qty, $buy, $sell, $order, $tid, $afile) = $sth->fetchrow_array) { 1124 | next unless $tid; 1125 | $tids{$tid} = [] unless $tids{$tid}; 1126 | 1127 | push @{$tids{$tid}}, '['.$id.','.$item.','.$qty.','.$buy.','.$sell.','.$order.','.$tid.',"'.$afile.'"]'; 1128 | } 1129 | $sth->finish; 1130 | 1131 | return unless %tids; 1132 | 1133 | while (my ($tid, $v) = each %tids) { 1134 | next unless ($v && @$v); 1135 | my $str = join (',', @$v); 1136 | 1137 | my $file = CACHE_DIR.'traders/'.$tid.'.sqf'; 1138 | open (OUT, ">$file"); 1139 | print OUT '['.$str.']'; 1140 | close (OUT); 1141 | } 1142 | 1143 | undef %tids; 1144 | } 1145 | --------------------------------------------------------------------------------