Remote collision screenshot

Started by vitovc, Aug 21, 2024, 05:07 PM

Previous topic - Next topic

vitovc


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) 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();
}
...::: vice city :::...
Useful things for vcmp: Relative position and finding an angle (in 3d), 3d line (like laser)

Gulk

Thats clever. thanks for sharing

Sebastian


habi2

what exactly does this do? Why is there a picture on the man's head.

vitovc

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
...::: vice city :::...
Useful things for vcmp: Relative position and finding an angle (in 3d), 3d line (like laser)

MEGAMIND

ingame printer 😎😅

habi2

#6
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.

PSL

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.

vitovc

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.
...::: vice city :::...
Useful things for vcmp: Relative position and finding an angle (in 3d), 3d line (like laser)