Vice City: Multiplayer

Server Development => Scripting and Server Management => Snippet Showroom => Topic started by: vitovc on Aug 21, 2024, 05:07 PM

Title: Remote collision screenshot
Post by: vitovc on Aug 21, 2024, 05:07 PM
(https://i.ibb.co/9VdssZV/colscreen.png) (https://ibb.co/b2Gzzb2)
https://i.imgur.com/fDo4Gnh.png (https://i.imgur.com/fDo4Gnh.png)

This is one of my old ideas that I finally found time to code. This script is just small example how functions for relative position and finding an angle (in 3d) (https://forum.vc-mp.org/index.php?topic=9368.0) can be used. Remote collision screenshot can be helpful to detect map mods (and with little edit - vehicle collisions mods/cheats)

server.nut
colscreen_i <- 0;

function onScriptLoad(){
  ::SetBackfaceCullingDisabled(true);
  ::SetMaxPlayers(12);
  ::SetUseClasses(true);
  ::SetServerName("Collision screenshot script");
  ::SetGameModeName("snippet");
  ::SetFriendlyFire(false);
  ::SetShowOnRadar(true);
  ::SetShowOnlyTeamMarkers(false);
  ::SetSpawnPlayerPos(::Vector(191.924, -468.823, 17.547));
  ::SetSpawnCameraPos(::Vector(194.041, -467.79, 17.547));
  ::SetSpawnCameraLook(::Vector(191.924, -468.823, 17.547));
  ::SetSyncFrameLimiter(false);
  ::SetFrameLimiter(false);
  ::SetTaxiBoostJump(true);
  ::SetDriveOnWater(false);
  ::SetFastSwitch(false);
  ::SetJumpSwitch(true);
  ::SetWallglitch(true);
  ::SetDrivebyEnabled(true);
  ::SetPerfectHandling(false);
  ::SetFlyingCars(false);
  ::SetDeathMessages(false);
  ::SetJoinMessages(false);
  ::SetWeather(4);
  ::SetHour(12);
  ::SetMinute(0);
  ::SetTimeRate(0);
  ::SetGravity(0.008);
  ::SetGamespeed(1.0);
  if("SetFallTimer" in ::getroottable()){
    ::SetFallTimer(500);// rel006 feature
  }
  ::SetWaterLevel(6.0);
  ::SetShootInAir(false);
  ::SetShowNametags(true);
  ::SetStuntBike(false);
  ::SetPassword("");
  ::SetVehiclesForcedRespawnHeight(19999);
  ::SetMaxHeight(300);
  ::AddClass(161, ::RGB(0, 200, 180), 0, ::Vector(191.924, -468.823, 17.547), -57.2958, 0, 0, 0, 0, 0, 0);
  ::SetWastedSettings(1500, 1500, 0.1, 0.1, ::RGB(0,0,0), 1000, 500);
  ::SetKillDelay(100);
}
function onLoginAttempt(playerName, password, ipAddress){
  return true;
}
function onPlayerJoin(player){
    player.Name = player.Name + player.ID;
}
function onPlayerRequestClass(player, classID, team, skin){
  return true;
}
function onPlayerRequestSpawn(player){
  return true;
}
function onPlayerCommand(player, cmd, text){

    if(cmd == "colscreen"){
        if(!text){return ::MessagePlayer("Error - command syntax: /colscreen <player ID> <depth>", player);}
        local text_arr = split(text, " ");
        if(text_arr.len() != 2){return ::MessagePlayer("Error - command syntax: /colscreen <player ID> <depth>", player);}
        local depth = text_arr[1].tointeger();
        if((depth < 10)||(depth > 1000)){
            return ::MessagePlayer("use depth from 10 to 1000", player);
        }
        if(text_arr[0]){
            local target = ::FindPlayer(text_arr[0].tointeger());
            if(target){
                ::colscreen_i++;
                local s = ::Stream();
                s.StartWrite();
                s.WriteByte(102);
                s.WriteByte(1);
                s.WriteInt(::colscreen_i); // uniq id of screenshot
                s.WriteByte(target.ID);
                s.WriteInt(depth);
                s.SendStream(player); // recipient
                ::MessagePlayer("Starting process of collision screenshot", player);
            } else {
                return ::MessagePlayer("Error - target player not found", player);
            }
        }
    } else if((cmd == "colscreenstop")||(cmd == "stopcolscreen")){
        if(!text){return ::MessagePlayer("Error - command syntax: /colscreenstop <player ID>", player);}
        local target = ::FindPlayer(text.tointeger());
        if(target){
            ::colscreen_i++;
            local s = ::Stream();
            s.StartWrite();
            s.WriteByte(102);
            s.WriteByte(5);
            s.SendStream(target);
            local s = ::Stream();
            s.StartWrite();
            s.WriteByte(102);
            s.WriteByte(6);
            s.SendStream(player);
            ::MessagePlayer("Process of collision screenshot is aborted", player);
        } else {
            return ::MessagePlayer("Error - target player not found", player);
        }
    }
}
function onClientScriptData(player){
    local netcode = ::Stream.ReadByte();
    if(netcode == 102){
        local colscreen_netcode = ::Stream.ReadByte();
        if(colscreen_netcode == 2){
            local colscreen_i = ::Stream.ReadInt();
            local target_id = ::Stream.ReadByte();
            local depth = ::Stream.ReadInt();
            local target = ::FindPlayer(target_id);
            if(target){
                local x1 = ::Stream.ReadFloat();
                local y1 = ::Stream.ReadFloat();
                local z1 = ::Stream.ReadFloat();
                local x2 = ::Stream.ReadFloat();
                local y2 = ::Stream.ReadFloat();
                local z2 = ::Stream.ReadFloat();
                local s = ::Stream();
                s.StartWrite();
                s.WriteByte(102);
                s.WriteByte(3);
                s.WriteInt(colscreen_i);
                s.WriteByte(player.ID); // recipient
                s.WriteInt(depth);
                s.WriteFloat(x1);
                s.WriteFloat(y1);
                s.WriteFloat(z1);
                s.WriteFloat(x2);
                s.WriteFloat(y2);
                s.WriteFloat(z2);
                s.SendStream(target);
            } else {
                return ::MessagePlayer("Error - target player not online anymore", player);
            }
        } else if(colscreen_netcode == 4){
            local colscreen_i = ::Stream.ReadInt();
            local recipient_id = ::Stream.ReadByte();
            local y = ::Stream.ReadInt();
            local l = ::Stream.ReadInt();
            local row = [];
            for(local i = 0; i < l; i++){
                row.push(::Stream.ReadByte());
            }
            local recipient = ::FindPlayer(recipient_id);
            if(recipient){
                local s = ::Stream();
                s.StartWrite();
                s.WriteByte(102);
                s.WriteByte(4);
                s.WriteInt(colscreen_i);
                s.WriteInt(y);
                s.WriteInt(l);
                for(local i = 0; i < l; i++){
                    s.WriteByte(row[i]);
                }
                s.SendStream(recipient);
            }
        }
    }
}

client (main.nut)
colscreen <- {
    "last_update" : {},
    "screenshot" : {},
    "last_call" : ::Script.GetTicks(),
    "recording" : false,
    "colscreen_i" : 0,
    "colscreen_i_recipient" : 0,
    "row_result" : [],
    "recipient_id" : 0,
    "depth" : 0,
    "x" : 0,
    "y" : 0,
    "x1" : 0,
    "y1" : 0,
    "z1" : 0,
    "x2" : 0,
    "y2" : 0,
    "z2" : 0,
    "angle" : 0,
    "vangle" : 0,
    "fov_per_pixel" : ::Vector(0.005, 0.007, 0), // its optimal for default screenshot_size
    "screenshot_size" : ::VectorScreen(252, 142), // resolution of screen, don't use big values to avoid waste of time
    "screenshot_sizeh" : false,
    "rays_per_frame" : 1000, // you can limit it for low-end PC; wont be used more than screenshot_size.X
    "start" : function(colscreen_i, recipient_id, depth, x1, y1, z1, x2, y2, z2){
        ::colscreen.colscreen_i = colscreen_i;
        ::colscreen.recipient_id = recipient_id;
        ::colscreen.depth = depth;
        ::colscreen.x1 = x1;
        ::colscreen.y1 = y1;
        ::colscreen.z1 = z1;
        ::colscreen.x2 = x2;
        ::colscreen.y2 = y2;
        ::colscreen.z2 = z2;

        ::colscreen.vangle = ::colscreen.get_vangle(x1, y1, z1, x2, y2, z2);
        ::colscreen.angle = ::colscreen.get_angle(x1, y1, x2, y2);
        ::colscreen.screenshot_sizeh = ::VectorScreen(::colscreen.screenshot_size.X * 0.5, ::colscreen.screenshot_size.Y * 0.5);
        ::colscreen.x = -::colscreen.screenshot_sizeh.X;
        ::colscreen.y = -::colscreen.screenshot_sizeh.Y;
        ::colscreen.row_result = [];
        ::colscreen.recording = true;
    },
    "normalize_angle" : function (a){
    return ::atan2(::sin(a), ::cos(a));
  },
    "get_vangle" : function(x1, y1, z1, x2, y2, z2){
        return ::atan2(z2 - z1, ::colscreen.dist_2d(x2,y2,x1,y1));
    },
    "get_angle" : function(x, y, x2, y2){
        return ::atan2(y2 - y, x2 - x);
    },
    "rel_pos3d" : function(dist, pos_x, pos_y, pos_z, y_angle, z_angle){
        local rX = dist * ::cos(y_angle) * ::cos(z_angle);
        local rY = dist * ::cos(y_angle) * ::sin(z_angle);
        local rZ = dist * ::sin(y_angle);
        return ::Vector(pos_x + rX, pos_y + rY, pos_z + rZ);
    },
    "dist_3d" : function (x, y, z, x2, y2, z2){
    return ::sqrt((x - x2)*(x - x2) + (y - y2)*(y - y2) + (z - z2)*(z - z2));
  },
    "dist_2d" : function (x, y, x2, y2){
    return ::sqrt((x - x2)*(x - x2) + (y - y2)*(y - y2));
  },
    "proc" : function(){
        if(true == ::colscreen.recording){
            if(::colscreen.rays_per_frame < 1){
                return false;
            }
            for(local i = 0; i < ::colscreen.rays_per_frame; i++){
                local rel_pos3d = ::colscreen.rel_pos3d(depth, x1, y1, z1, ::colscreen.normalize_angle(::colscreen.vangle + (-1 * (::colscreen.y * ::colscreen.fov_per_pixel.Y))), ::colscreen.normalize_angle(::colscreen.angle + ( (::colscreen.x * ::colscreen.fov_per_pixel.X))));
                local rt = ::RayTrace(::Vector(x1, y1, z1), rel_pos3d, RAY_BUILDING | RAY_OBJECT);
                local depth_color = 255;
                if(rt.Collided){
                    local dist = ::colscreen.dist_3d(x1, y1, z1, rt.Position.X, rt.Position.Y, rt.Position.Z);
                    depth_color = ((255 / depth.tofloat()) * dist).tointeger();
                    if(depth_color > 255){
                        depth_color = 255;
                    }
                }
                ::colscreen.row_result.push(depth_color);

                ::colscreen.x++;
                if(::colscreen.x >= ::colscreen.screenshot_sizeh.X){
                    local s = Stream();
                    s.WriteByte(102);
                    s.WriteByte(4);
                    s.WriteInt(::colscreen.colscreen_i);
                    s.WriteByte(::colscreen.recipient_id);
                    s.WriteInt(::colscreen.y);
                    local l = ::colscreen.row_result.len();
                    s.WriteInt(l);
                    for(local i = 0; i < l; i++){
                        s.WriteByte(::colscreen.row_result[i]);
                    }
                    ::Server.SendData(s);
                    ::colscreen.row_result = [];
                    ::colscreen.x = -::colscreen.screenshot_sizeh.X;
                    ::colscreen.y++;
                    if(::colscreen.y >= ::colscreen.screenshot_sizeh.Y){
                        ::colscreen.recording = false;
                    }
                    return true;
                }
            }
        }
  },
  "on_server_data" : function(stream){
        local colscreen_netcode = stream.ReadByte();
        if(colscreen_netcode == 1){
            ::colscreen.colscreen_i_recipient = stream.ReadInt();
            local target_id = stream.ReadByte();
            local depth = stream.ReadInt();
            local screen_size = ::GUI.GetScreenSize();
            local camera_pos_from = ::GUI.ScreenPosToWorld(::Vector(screen_size.X * 0.5, screen_size.Y * 0.5, -1));
            local camera_pos_to = ::GUI.ScreenPosToWorld(::Vector(screen_size.X * 0.5, screen_size.Y * 0.5, 1));
            local s = Stream();
            s.WriteByte(102);
            s.WriteByte(2);
            s.WriteInt(::colscreen.colscreen_i_recipient);
            s.WriteByte(target_id);
            s.WriteInt(depth);
            s.WriteFloat(camera_pos_from.X);
            s.WriteFloat(camera_pos_from.Y);
            s.WriteFloat(camera_pos_from.Z);
            s.WriteFloat(camera_pos_to.X);
            s.WriteFloat(camera_pos_to.Y);
            s.WriteFloat(camera_pos_to.Z);
            ::Server.SendData(s);
        } else if(colscreen_netcode == 3){
            local colscreen_i = stream.ReadInt();
            local recipient_id = stream.ReadByte();
            local depth = stream.ReadInt();
            local x1 = stream.ReadFloat();
            local y1 = stream.ReadFloat();
            local z1 = stream.ReadFloat();
            local x2 = stream.ReadFloat();
            local y2 = stream.ReadFloat();
            local z2 = stream.ReadFloat();
            ::colscreen.start(colscreen_i, recipient_id, depth, x1, y1, z1, x2, y2, z2);
        } else if(colscreen_netcode == 4){
            local colscreen_i = stream.ReadInt();
            if(colscreen_i == ::colscreen.colscreen_i_recipient){
                local y = stream.ReadInt();
                local l = stream.ReadInt();
                local screen_size = ::GUI.GetScreenSize();
                local screenshot_pos = ::VectorScreen((screen_size.X * 0.5), (screen_size.Y * 0.5));
                for(local i = 0; i < l; i++){
                    local x = (::colscreen.screenshot_size.X * 0.5).tointeger() - i;
                    local pixel = stream.ReadByte();
                    local c = ::GUICanvas();
                    c.Size = ::VectorScreen(1, 1);
                    c.Position = ::VectorScreen(screenshot_pos.X + x, screenshot_pos.Y + y);
                    c.Colour = ::Colour(pixel, pixel, pixel, 255);
                    ::colscreen.screenshot.rawset(x +"."+ y, c);
                }
            }
        } else if(colscreen_netcode == 5){
            ::colscreen.recording = false;
            ::colscreen.row_result = [];
        } else if(colscreen_netcode == 6){
            ::colscreen.screenshot = {};
        }
    }
}

function Server::ServerData(stream){
  local netcode = stream.ReadByte();
  if(netcode == 102){
  ::colscreen.on_server_data(stream);
  }
}
function Script::ScriptProcess(){
  ::colscreen.proc();
}
Title: Re: Remote collision screenshot
Post by: Gulk on Aug 22, 2024, 04:26 PM
Thats clever. thanks for sharing
Title: Re: Remote collision screenshot
Post by: Sebastian on Aug 28, 2024, 09:39 PM
Woah! This is big!
Title: Re: Remote collision screenshot
Post by: habi2 on Aug 29, 2024, 04:08 AM
what exactly does this do? Why is there a picture on the man's head.
Title: Re: Remote collision screenshot
Post by: vitovc on Aug 29, 2024, 06:45 AM
Quote from: habi2 on Aug 29, 2024, 04:08 AMwhat exactly does this do? Why is there a picture on the man's head.
this takes camera position from admin (guy who typed command), then send this position to target player, on side of target player: script send raytrace from camera position to world, and send back relative distance (0-255, based on <depth>) row by row back to admin to draw picture of raytrace image
Title: Re: Remote collision screenshot
Post by: MEGAMIND on Aug 29, 2024, 06:54 AM
ingame printer 😎😅
Title: Re: Remote collision screenshot
Post by: habi2 on Aug 29, 2024, 07:58 AM
Quote from: vitovc on Aug 29, 2024, 06:45 AMthis takes camera position from admin (guy who typed command), then send this position to target player, on side of target player: script send raytrace from camera position to world, and send back relative distance (0-255, based on <depth>) row by row back to admin to draw picture of raytrace image
So that picture above tommy's head is a depth-row-by-row raytrace result you made.
God, now i understand.

Good work.
Title: Re: Remote collision screenshot
Post by: PSL on Oct 30, 2024, 06:13 AM
This feature seems to be able to test if the player has made changes to the map, such as deleting a building, etc., should be able to use this feature to see, I will try to modify your code for real-time monitoring of the player's screen, thank you for making and sharing this very cool feature.
Title: Re: Remote collision screenshot
Post by: vitovc on Oct 30, 2024, 09:04 AM
Quote from: PSL on Oct 30, 2024, 06:13 AMI will try to modify your code for real-time monitoring of the player's screen
Raytrace function is using CPU too much, real-time is possible only for very low-resolution and low recoding frames per second.