mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-07-16 08:55:18 +00:00
command experience calculations upon facility capture or resecure
This commit is contained in:
parent
8bdb215d58
commit
77066f962e
48 changed files with 1544 additions and 1020 deletions
|
|
@ -17,9 +17,10 @@ CREATE TABLE IF NOT EXISTS "sessionnumber" (
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS "killactivity" (
|
CREATE TABLE IF NOT EXISTS "killactivity" (
|
||||||
"index" SERIAL PRIMARY KEY NOT NULL,
|
"index" SERIAL PRIMARY KEY NOT NULL,
|
||||||
"victim_id" INT NOT NULL REFERENCES avatar (id),
|
|
||||||
"killer_id" INT NOT NULL REFERENCES avatar (id),
|
"killer_id" INT NOT NULL REFERENCES avatar (id),
|
||||||
|
"victim_id" INT NOT NULL REFERENCES avatar (id),
|
||||||
"victim_exosuit" SMALLINT NOT NULL,
|
"victim_exosuit" SMALLINT NOT NULL,
|
||||||
|
"victim_mounted" INT NOT NULL DEFAULT 0,
|
||||||
"weapon_id" SMALLINT NOT NULL,
|
"weapon_id" SMALLINT NOT NULL,
|
||||||
"zone_id" SMALLINT NOT NULL,
|
"zone_id" SMALLINT NOT NULL,
|
||||||
"px" INT NOT NULL,
|
"px" INT NOT NULL,
|
||||||
|
|
@ -33,7 +34,7 @@ CREATE TABLE IF NOT EXISTS "kda" (
|
||||||
"avatar_id" INT NOT NULL REFERENCES avatar (id),
|
"avatar_id" INT NOT NULL REFERENCES avatar (id),
|
||||||
"kills" INT NOT NULL DEFAULT 0,
|
"kills" INT NOT NULL DEFAULT 0,
|
||||||
"deaths" INT NOT NULL DEFAULT 0,
|
"deaths" INT NOT NULL DEFAULT 0,
|
||||||
"assists" INT NOT NULL DEFAULT 0,
|
"revives" INT NOT NULL DEFAULT 0,
|
||||||
UNIQUE(avatar_id)
|
UNIQUE(avatar_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -42,7 +43,7 @@ CREATE TABLE IF NOT EXISTS "kdasession" (
|
||||||
"session_id" INT NOT NULL,
|
"session_id" INT NOT NULL,
|
||||||
"kills" INT NOT NULL DEFAULT 0,
|
"kills" INT NOT NULL DEFAULT 0,
|
||||||
"deaths" INT NOT NULL DEFAULT 0,
|
"deaths" INT NOT NULL DEFAULT 0,
|
||||||
"assists" INT NOT NULL DEFAULT 0,
|
"revives" INT NOT NULL DEFAULT 0,
|
||||||
UNIQUE(avatar_id, session_id)
|
UNIQUE(avatar_id, session_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -73,89 +74,144 @@ CREATE TABLE IF NOT EXISTS "legacykills" (
|
||||||
UNIQUE(avatar_id)
|
UNIQUE(avatar_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE VIEW kdacampaign AS (
|
CREATE TABLE IF NOT EXISTS "machinedestroyed" (
|
||||||
SELECT
|
"index" SERIAL PRIMARY KEY NOT NULL,
|
||||||
session.avatar_id,
|
"avatar_id" INT NOT NULL REFERENCES avatar (id),
|
||||||
SUM(session.kills) AS kills,
|
"weapon_id" INT NOT NULL,
|
||||||
SUM(session.deaths) AS deaths,
|
"machine_type" INT NOT NULL,
|
||||||
SUM(session.assists) AS assists,
|
"machine_faction" SMALLINT NOT NULL,
|
||||||
COUNT(session.avatar_id) AS numberOfSessions
|
"hacked_faction" SMALLINT NOT NULL,
|
||||||
FROM (
|
"as_cargo" BOOLEAN NOT NULL,
|
||||||
SELECT avatar_id, session_id, kills, deaths, assists
|
"zone_num" SMALLINT NOT NULL,
|
||||||
FROM kdasession
|
"px" INT NOT NULL,
|
||||||
UNION ALL
|
"py" INT NOT NULL,
|
||||||
SELECT avatar_id, 0, kills, deaths, assists
|
"pz" INT NOT NULL,
|
||||||
FROM kda
|
"timestamp" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
) AS session
|
|
||||||
LEFT JOIN kda
|
|
||||||
ON kda.avatar_id = session.avatar_id
|
|
||||||
GROUP BY session.avatar_id
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE VIEW weaponstatcampaign AS (
|
CREATE TABLE IF NOT EXISTS "assistactivity" (
|
||||||
SELECT
|
"index" SERIAL PRIMARY KEY NOT NULL,
|
||||||
weaponstat.avatar_id,
|
"victim_id" INT NOT NULL REFERENCES avatar (id),
|
||||||
weaponstat.weapon_id,
|
"attacker_id" INT NOT NULL REFERENCES avatar (id),
|
||||||
SUM(session.shots_fired) AS shots_fired,
|
"weapon_id" SMALLINT NOT NULL,
|
||||||
SUM(session.shots_landed) AS shots_landed,
|
"zone_id" SMALLINT NOT NULL,
|
||||||
SUM(session.kills) AS kills,
|
"px" INT NOT NULL,
|
||||||
SUM(session.assists) AS assists,
|
"py" INT NOT NULL,
|
||||||
COUNT(session.session_id) AS numberOfSessions
|
"pz" INT NOT NULL,
|
||||||
FROM (
|
"exp" INT NOT NULL,
|
||||||
SELECT avatar_id, weapon_id, session_id, shots_fired, shots_landed, kills, assists
|
"timestamp" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
FROM weaponstatsession
|
|
||||||
UNION ALL
|
|
||||||
SELECT avatar_id, weapon_id, 0, shots_fired, shots_landed, kills, assists
|
|
||||||
FROM weaponstat
|
|
||||||
) AS session
|
|
||||||
LEFT JOIN weaponstat
|
|
||||||
ON weaponstat.avatar_id = session.avatar_id
|
|
||||||
GROUP BY weaponstat.avatar_id, weaponstat.weapon_id
|
|
||||||
);
|
);
|
||||||
|
|
||||||
/*
|
CREATE TABLE IF NOT EXISTS "supportactivity" (
|
||||||
Procedure for initializing and increasing the session number.
|
"index" SERIAL PRIMARY KEY NOT NULL,
|
||||||
Always indexes a new session.
|
"user_id" INT NOT NULL REFERENCES avatar (id), -- player that provides the support
|
||||||
*/
|
"target_id" INT NOT NULL REFERENCES avatar (id), -- benefactor of the support
|
||||||
CREATE OR REPLACE PROCEDURE proc_sessionnumber_initAndOrIncrease
|
"target_exosuit" SMALLINT NOT NULL, -- benefactor's exo-suit
|
||||||
(avatarId IN Int, number OUT Int)
|
"interaction_type" SMALLINT NOT NULL, -- classification of support
|
||||||
AS
|
"intermediate_type" INT DEFAULT 0, -- through what medium user_id supports target_id
|
||||||
$$
|
"implement_type" INT DEFAULT 0, -- tool utilized by user_id to support target_id, potentially via interaction with intermediate_type
|
||||||
BEGIN
|
"exp" INT NOT NULL,
|
||||||
SELECT (MAX(session_id,0)+1) INTO number
|
"timestamp" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
FROM sessionnumber
|
);
|
||||||
WHERE avatar_id = avatarId;
|
|
||||||
INSERT INTO sessionnumber
|
CREATE TABLE IF NOT EXISTS "ntuactivity" (
|
||||||
VALUES (avatarId, number);
|
"avatar_id" INT NOT NULL REFERENCES avatar (id),
|
||||||
END;
|
"zone_id" INT NOT NULL,
|
||||||
$$ LANGUAGE plpgsql;
|
"building_id" INT NOT NULL,
|
||||||
|
"exp" INT NOT NULL,
|
||||||
|
UNIQUE(avatar_id, zone_id, building_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS "buildingcapture" (
|
||||||
|
"index" SERIAL PRIMARY KEY NOT NULL,
|
||||||
|
"avatar_id" INT NOT NULL REFERENCES avatar (id),
|
||||||
|
"zone_id" INT NOT NULL,
|
||||||
|
"building_id" INT NOT NULL,
|
||||||
|
"exp" INT NOT NULL,
|
||||||
|
"exp_type" CHAR(3) NOT NULL, /* bep, cep, llu */
|
||||||
|
"timestamp" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS "expbuildingcapture" (
|
||||||
|
"avatar_id" INT NOT NULL REFERENCES avatar (id),
|
||||||
|
"zone_id" INT NOT NULL,
|
||||||
|
"building_id" INT NOT NULL,
|
||||||
|
"captures" INT NOT NULL,
|
||||||
|
"bep" INT NOT NULL,
|
||||||
|
"cep" INT NOT NULL,
|
||||||
|
UNIQUE(avatar_id, zone_id, building_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS "llubuildingcapture" (
|
||||||
|
"avatar_id" INT NOT NULL REFERENCES avatar (id),
|
||||||
|
"zone_id" INT NOT NULL,
|
||||||
|
"building_id" INT NOT NULL,
|
||||||
|
"captures" INT NOT NULL,
|
||||||
|
"exp" INT NOT NULL,
|
||||||
|
UNIQUE(avatar_id, zone_id, building_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS "progressiondebt" (
|
||||||
|
"avatar_id" INT NOT NULL REFERENCES avatar (id),
|
||||||
|
"experience" INT NOT NULL DEFAULT 0,
|
||||||
|
UNIQUE(avatar_id)
|
||||||
|
);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Procedure for initializing and increasing the session number.
|
Procedure for initializing and increasing the session number.
|
||||||
Index for a new session only if last session was created more than one hour ago.
|
Index for a new session only if last session was created more than one hour ago.
|
||||||
*/
|
*/
|
||||||
CREATE OR REPLACE PROCEDURE proc_sessionnumber_initAndOrIncreasePerHour
|
CREATE OR REPLACE FUNCTION proc_sessionnumber_test
|
||||||
(avatarId IN Int, number OUT Int, nextNumber OUT Int)
|
(avatarId integer)
|
||||||
|
RETURNS integer
|
||||||
AS
|
AS
|
||||||
$$
|
$$
|
||||||
DECLARE time TIMESTAMP;
|
DECLARE time TIMESTAMP;
|
||||||
|
DECLARE number integer;
|
||||||
BEGIN
|
BEGIN
|
||||||
SELECT MAX(session_id,0) INTO number
|
SELECT MAX(session_id) INTO number
|
||||||
FROM sessionnumber
|
FROM sessionnumber
|
||||||
WHERE avatar_id = avatarId;
|
WHERE avatar_id = avatarId;
|
||||||
SELECT timestamp INTO time
|
SELECT COALESCE(timestamp) INTO time
|
||||||
FROM sessionnumber
|
FROM sessionnumber
|
||||||
WHERE avatar_id = avatarId AND session_id = number;
|
WHERE avatar_id = avatarId AND session_id = number;
|
||||||
IF (CAST(CURRENT_TIMESTAMP AS FLOAT) > CAST(DATE_ADD('hour', 1, DATE_TRUNC('hour', time)) AS FLOAT)) THEN
|
IF (time IS null) THEN
|
||||||
|
number := 0;
|
||||||
|
END IF;
|
||||||
|
RETURN number;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION proc_sessionnumber_initAndOrIncreasePerHour
|
||||||
|
(avatarId integer)
|
||||||
|
RETURNS integer
|
||||||
|
AS
|
||||||
|
$$
|
||||||
|
DECLARE time TIMESTAMP;
|
||||||
|
DECLARE number integer;
|
||||||
|
DECLARE nextNumber integer;
|
||||||
|
BEGIN
|
||||||
|
SELECT MAX(session_id) INTO number
|
||||||
|
FROM sessionnumber
|
||||||
|
WHERE avatar_id = avatarId;
|
||||||
|
SELECT COALESCE(timestamp) INTO time
|
||||||
|
FROM sessionnumber
|
||||||
|
WHERE avatar_id = avatarId AND session_id = number;
|
||||||
|
IF (time IS null) THEN
|
||||||
|
nextNumber := 1;
|
||||||
|
INSERT INTO sessionnumber
|
||||||
|
VALUES (avatarId, nextNumber);
|
||||||
|
ELSIF (CURRENT_TIMESTAMP > DATE_TRUNC('hour', time) + interval '1' hour) THEN
|
||||||
nextNumber := number + 1;
|
nextNumber := number + 1;
|
||||||
INSERT INTO sessionnumber
|
INSERT INTO sessionnumber
|
||||||
VALUES (avatarId, number);
|
VALUES (avatarId, nextNumber);
|
||||||
ELSE
|
ELSE
|
||||||
nextNumber := number;
|
nextNumber := number;
|
||||||
UPDATE sessionnumber
|
UPDATE sessionnumber
|
||||||
SET timestamp = CURRENT_TIMESTAMP
|
SET timestamp = CURRENT_TIMESTAMP
|
||||||
WHERE avatar_id = avatarId AND session_id = number;
|
WHERE avatar_id = avatarId AND session_id = number;
|
||||||
END IF;
|
END IF;
|
||||||
|
RETURN nextNumber;
|
||||||
END;
|
END;
|
||||||
$$ LANGUAGE plpgsql;
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
|
@ -163,24 +219,35 @@ $$ LANGUAGE plpgsql;
|
||||||
Procedure for accessing any existing session number.
|
Procedure for accessing any existing session number.
|
||||||
Actually polls the previous session number from the account table.
|
Actually polls the previous session number from the account table.
|
||||||
*/
|
*/
|
||||||
CREATE OR REPLACE PROCEDURE proc_sessionnumber_get
|
CREATE OR REPLACE FUNCTION proc_sessionnumber_get
|
||||||
(avatarId IN Int, number OUT Int)
|
(avatarId integer)
|
||||||
|
RETURNS integer
|
||||||
AS
|
AS
|
||||||
$$
|
$$
|
||||||
|
DECLARE sessionId integer;
|
||||||
|
DECLARE number integer;
|
||||||
BEGIN
|
BEGIN
|
||||||
SELECT COALESCE(session_id,0) INTO number
|
SELECT COALESCE(session_id) INTO sessionId
|
||||||
FROM account
|
FROM account
|
||||||
WHERE avatar_logged_in = avatarId;
|
WHERE avatar_logged_in = avatarId;
|
||||||
|
IF sessionId IS NULL THEN
|
||||||
|
number := 0;
|
||||||
|
ELSE
|
||||||
|
number := sessionId;
|
||||||
|
END IF;
|
||||||
|
RETURN number;
|
||||||
END;
|
END;
|
||||||
$$ LANGUAGE plpgsql;
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Procedures for ensuring that row entries can be found in specified tables before some other DML operation updates those row entries.
|
Procedures for ensuring that row entries can be found in specified tables before some other DML operation updates those row entries.
|
||||||
*/
|
*/
|
||||||
CREATE OR REPLACE PROCEDURE proc_kda_addEntryIfNone
|
CREATE OR REPLACE FUNCTION proc_kda_addEntryIfNone
|
||||||
(avatarId IN Int)
|
(avatarId integer)
|
||||||
|
RETURNS integer
|
||||||
AS
|
AS
|
||||||
$$
|
$$
|
||||||
|
DECLARE out integer;
|
||||||
BEGIN
|
BEGIN
|
||||||
IF EXISTS(
|
IF EXISTS(
|
||||||
SELECT *
|
SELECT *
|
||||||
|
|
@ -189,43 +256,115 @@ BEGIN
|
||||||
HAVING COUNT(*) = 0) THEN
|
HAVING COUNT(*) = 0) THEN
|
||||||
INSERT INTO kda (avatar_id)
|
INSERT INTO kda (avatar_id)
|
||||||
VALUES (avatarId);
|
VALUES (avatarId);
|
||||||
|
out := 1;
|
||||||
|
ELSE
|
||||||
|
out := 0;
|
||||||
END IF;
|
END IF;
|
||||||
|
RETURN out;
|
||||||
END;
|
END;
|
||||||
$$ LANGUAGE plpgsql;
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
CREATE OR REPLACE PROCEDURE proc_weaponstat_addEntryIfNone
|
CREATE OR REPLACE FUNCTION proc_weaponstat_addEntryIfNone
|
||||||
(avatarId IN Int, weaponId IN Int)
|
(avatarId integer, weaponId integer)
|
||||||
|
RETURNS integer
|
||||||
AS
|
AS
|
||||||
$$
|
$$
|
||||||
DECLARE sessionId Int;
|
DECLARE out integer;
|
||||||
BEGIN
|
BEGIN
|
||||||
SELECT proc_sessionnumber_get(avatarId, sessionId);
|
IF NOT EXISTS(
|
||||||
IF EXISTS(
|
SELECT avatar_id, weapon_id
|
||||||
SELECT *
|
|
||||||
FROM weaponstat
|
FROM weaponstat
|
||||||
WHERE avatar_id = avatarId AND weapon_id = weaponId
|
WHERE avatar_id = avatarId AND weapon_id = weaponId
|
||||||
HAVING COUNT(*) = 0) THEN
|
GROUP BY avatar_id, weapon_id
|
||||||
INSERT INTO weaponstat (avatar_id, session_id, weapon_id)
|
) THEN
|
||||||
VALUES (avatarId, sessionId, weaponId);
|
INSERT INTO weaponstat (avatar_id, weapon_id)
|
||||||
|
VALUES (avatarId, weaponId);
|
||||||
|
out := 1;
|
||||||
|
ELSE
|
||||||
|
out := 0;
|
||||||
END IF;
|
END IF;
|
||||||
|
RETURN out;
|
||||||
END;
|
END;
|
||||||
$$ LANGUAGE plpgsql;
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
CREATE OR REPLACE PROCEDURE proc_weaponstatsession_addEntryIfNone
|
CREATE OR REPLACE FUNCTION proc_weaponstatsession_addEntryIfNoneWithSessionId
|
||||||
(avatarId IN Int, weaponId IN Int)
|
(avatarId integer, weaponId integer, sessionId integer)
|
||||||
|
RETURNS integer
|
||||||
|
AS
|
||||||
|
$$
|
||||||
|
DECLARE out integer;
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS(
|
||||||
|
SELECT avatar_id, session_id, weapon_id
|
||||||
|
FROM weaponstatsession
|
||||||
|
WHERE avatar_id = avatarId AND weapon_id = weaponId AND session_id = sessionId
|
||||||
|
GROUP BY avatar_id, session_id, weapon_id
|
||||||
|
) THEN
|
||||||
|
INSERT INTO weaponstatsession (avatar_id, session_id, weapon_id)
|
||||||
|
VALUES (avatarId, sessionId, weaponId);
|
||||||
|
out := 1;
|
||||||
|
ELSE
|
||||||
|
out := 0;
|
||||||
|
END IF;
|
||||||
|
RETURN out;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION proc_weaponstatsession_addEntryIfNone
|
||||||
|
(avatarId integer, weaponId integer)
|
||||||
|
RETURNS integer
|
||||||
AS
|
AS
|
||||||
$$
|
$$
|
||||||
DECLARE sessionId Int;
|
DECLARE sessionId Int;
|
||||||
BEGIN
|
BEGIN
|
||||||
SELECT proc_sessionnumber_get(avatarId, sessionId);
|
sessionId := proc_sessionnumber_get(avatarId);
|
||||||
IF EXISTS(
|
RETURN proc_weaponstatsession_addEntryIfNoneWithSessionId(avatarId, weaponId, sessionId);
|
||||||
SELECT *
|
END;
|
||||||
FROM weaponstatsession
|
$$ LANGUAGE plpgsql;
|
||||||
WHERE avatar_id = avatarId AND weapon_id = weaponId
|
|
||||||
HAVING COUNT(*) = 0) THEN
|
CREATE OR REPLACE FUNCTION proc_expbuildingcapture_addEntryIfNone
|
||||||
INSERT INTO weaponstatsession (avatar_id, session_id, weapon_id)
|
(avatarId integer, buildingId integer, zoneId integer)
|
||||||
VALUES (avatarId, sessionId, weaponId);
|
RETURNS integer
|
||||||
|
AS
|
||||||
|
$$
|
||||||
|
DECLARE out integer;
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS(
|
||||||
|
SELECT avatar_id, building_id, zone_id
|
||||||
|
FROM expbuildingcapture
|
||||||
|
WHERE avatar_id = avatarId AND building_id = buildingId AND zone_id = zoneId
|
||||||
|
GROUP BY avatar_id, building_id, zone_id
|
||||||
|
) THEN
|
||||||
|
INSERT INTO expbuildingcapture (avatar_id, building_id, zone_id, captures, bep, cep)
|
||||||
|
VALUES (avatarId, building_id, zone_id, 0, 0, 0);
|
||||||
|
out := 1;
|
||||||
|
ELSE
|
||||||
|
out := 0;
|
||||||
END IF;
|
END IF;
|
||||||
|
RETURN out;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION proc_llubuildingcapture_addEntryIfNone
|
||||||
|
(avatarId integer, buildingId integer, zoneId integer)
|
||||||
|
RETURNS integer
|
||||||
|
AS
|
||||||
|
$$
|
||||||
|
DECLARE out integer;
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS(
|
||||||
|
SELECT avatar_id, building_id, zone_id
|
||||||
|
FROM llubuildingcapture
|
||||||
|
WHERE avatar_id = avatarId AND building_id = buildingId AND zone_id = zoneId
|
||||||
|
GROUP BY avatar_id, building_id, zone_id
|
||||||
|
) THEN
|
||||||
|
INSERT INTO llubuildingcapture (avatar_id, building_id, zone_id, captures, exp)
|
||||||
|
VALUES (avatarId, building_id, zone_id, 0, 0);
|
||||||
|
out := 1;
|
||||||
|
ELSE
|
||||||
|
out := 0;
|
||||||
|
END IF;
|
||||||
|
RETURN out;
|
||||||
END;
|
END;
|
||||||
$$ LANGUAGE plpgsql;
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
|
@ -243,11 +382,12 @@ DECLARE sessionId Int;
|
||||||
DECLARE oldSessionId Int;
|
DECLARE oldSessionId Int;
|
||||||
BEGIN
|
BEGIN
|
||||||
avatarId := NEW.avatar_logged_in;
|
avatarId := NEW.avatar_logged_in;
|
||||||
SELECT proc_sessionnumber_initAndOrIncreasePerHour(avatarId, oldSessionId, sessionId);
|
oldSessionId := proc_sessionnumber_test(avatarId);
|
||||||
|
sessionId := proc_sessionnumber_initAndOrIncreasePerHour(avatarId);
|
||||||
IF (sessionId > oldSessionId) THEN
|
IF (sessionId > oldSessionId) THEN
|
||||||
BEGIN
|
BEGIN
|
||||||
UPDATE account
|
UPDATE account
|
||||||
SET sessionId = sessionId
|
SET session_id = sessionId
|
||||||
WHERE id = OLD.id;
|
WHERE id = OLD.id;
|
||||||
INSERT INTO kdasession (avatar_id, session_id)
|
INSERT INTO kdasession (avatar_id, session_id)
|
||||||
VALUES (avatarId, sessionId);
|
VALUES (avatarId, sessionId);
|
||||||
|
|
@ -258,7 +398,7 @@ END;
|
||||||
$$ LANGUAGE plpgsql;
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
CREATE OR REPLACE TRIGGER psf_account_newSession
|
CREATE OR REPLACE TRIGGER psf_account_newSession
|
||||||
BEFORE UPDATE
|
AFTER UPDATE
|
||||||
OF avatar_logged_in
|
OF avatar_logged_in
|
||||||
ON account
|
ON account
|
||||||
FOR EACH ROW
|
FOR EACH ROW
|
||||||
|
|
@ -281,12 +421,13 @@ DECLARE victimSessionId Int;
|
||||||
DECLARE killerId Int;
|
DECLARE killerId Int;
|
||||||
DECLARE victimId Int;
|
DECLARE victimId Int;
|
||||||
DECLARE weaponId Int;
|
DECLARE weaponId Int;
|
||||||
|
DECLARE out integer;
|
||||||
BEGIN
|
BEGIN
|
||||||
killerId := NEW.killer_id;
|
killerId := NEW.killer_id;
|
||||||
victimId := NEW.victim_id;
|
victimId := NEW.victim_id;
|
||||||
weaponId := NEW.weapon_id;
|
weaponId := NEW.weapon_id;
|
||||||
SELECT proc_sessionnumber_get(killerId, killerSessionId);
|
killerSessionId := proc_sessionnumber_get(killerId);
|
||||||
SELECT proc_sessionnumber_get(victimId, victimSessionId);
|
victimSessionId := proc_sessionnumber_get(victimId);
|
||||||
BEGIN
|
BEGIN
|
||||||
UPDATE kdasession
|
UPDATE kdasession
|
||||||
SET kills = kills + 1
|
SET kills = kills + 1
|
||||||
|
|
@ -298,6 +439,7 @@ BEGIN
|
||||||
WHERE avatar_id = victimId AND session_id = victimSessionId;
|
WHERE avatar_id = victimId AND session_id = victimSessionId;
|
||||||
END;
|
END;
|
||||||
BEGIN
|
BEGIN
|
||||||
|
out := proc_weaponstatsession_addEntryIfNoneWithSessionId(killerId, weaponId, killerSessionId);
|
||||||
UPDATE weaponstatsession
|
UPDATE weaponstatsession
|
||||||
SET kills = kills + 1
|
SET kills = kills + 1
|
||||||
WHERE avatar_id = killerId AND session_id = killerSessionId AND weapon_id = weaponId;
|
WHERE avatar_id = killerId AND session_id = killerSessionId AND weapon_id = weaponId;
|
||||||
|
|
@ -323,10 +465,12 @@ AS
|
||||||
$$
|
$$
|
||||||
DECLARE avatarId Int;
|
DECLARE avatarId Int;
|
||||||
DECLARE weaponId Int;
|
DECLARE weaponId Int;
|
||||||
|
DECLARE out integer;
|
||||||
BEGIN
|
BEGIN
|
||||||
avatarId := NEW.avatar_id;
|
avatarId := NEW.avatar_id;
|
||||||
weaponId := NEW.weapon_id;
|
weaponId := NEW.weapon_id;
|
||||||
SELECT proc_weaponstatsession_addEntryIfNone(avatarId, weaponId);
|
out := proc_weaponstatsession_addEntryIfNone(avatarId, weaponId);
|
||||||
|
RETURN NEW;
|
||||||
END;
|
END;
|
||||||
$$ LANGUAGE plpgsql;
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
|
@ -346,9 +490,11 @@ RETURNS TRIGGER
|
||||||
AS
|
AS
|
||||||
$$
|
$$
|
||||||
DECLARE avatarId Int;
|
DECLARE avatarId Int;
|
||||||
|
DECLARE out integer;
|
||||||
BEGIN
|
BEGIN
|
||||||
avatarId := NEW.avatar_id;
|
avatarId := NEW.avatar_id;
|
||||||
SELECT proc_kda_addEntryIfNone(avatarId);
|
out := proc_kda_addEntryIfNone(avatarId);
|
||||||
|
RETURN NEW;
|
||||||
END;
|
END;
|
||||||
$$ LANGUAGE plpgsql;
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
|
@ -369,10 +515,12 @@ AS
|
||||||
$$
|
$$
|
||||||
DECLARE avatarId Int;
|
DECLARE avatarId Int;
|
||||||
DECLARE weaponId Int;
|
DECLARE weaponId Int;
|
||||||
|
DECLARE out integer;
|
||||||
BEGIN
|
BEGIN
|
||||||
avatarId := NEW.avatar_id;
|
avatarId := NEW.avatar_id;
|
||||||
weaponId := NEW.weapon_id;
|
weaponId := NEW.weapon_id;
|
||||||
SELECT proc_weaponstat_addEntryIfNone(avatarId, weaponId);
|
out := proc_weaponstat_addEntryIfNone(avatarId, weaponId);
|
||||||
|
RETURN NEW;
|
||||||
END;
|
END;
|
||||||
$$ LANGUAGE plpgsql;
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
|
@ -382,10 +530,40 @@ ON weaponstat
|
||||||
FOR EACH ROW
|
FOR EACH ROW
|
||||||
EXECUTE FUNCTION fn_weaponstat_addEntryIfNone();
|
EXECUTE FUNCTION fn_weaponstat_addEntryIfNone();
|
||||||
|
|
||||||
|
/*
|
||||||
|
Before attempting to update the revival data in a character's session kda column,
|
||||||
|
set the session id to the current value then
|
||||||
|
manually update the column, overriding the process.
|
||||||
|
*/
|
||||||
|
CREATE OR REPLACE FUNCTION fn_kdasession_updateRevives()
|
||||||
|
RETURNS TRIGGER
|
||||||
|
AS
|
||||||
|
$$
|
||||||
|
DECLARE avatarId Int;
|
||||||
|
DECLARE sessionId Int;
|
||||||
|
DECLARE newRevives Int;
|
||||||
|
BEGIN
|
||||||
|
avatarId := NEW.avatar_id;
|
||||||
|
sessionId := proc_sessionnumber_get(avatarId);
|
||||||
|
newRevives := NEW.revives;
|
||||||
|
if (newRevives > 0) THEN
|
||||||
|
UPDATE kdasession
|
||||||
|
SET revives = revives + newRevives
|
||||||
|
WHERE avatar_id = avatarId AND session_id = sessionId;
|
||||||
|
END IF;
|
||||||
|
RETURN NULL;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE OR REPLACE TRIGGER psf_kdasession_updateRevives
|
||||||
|
BEFORE INSERT
|
||||||
|
ON kdasession
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION fn_kdasession_updateRevives();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Upon deletion of row entries for a character's session KDA,
|
Upon deletion of row entries for a character's session KDA,
|
||||||
the values are copied over to the campaign KDA record for that character.
|
the values are copied over to the campaign KDA record for that character.
|
||||||
This will fire mainly when called by the trigger for login (above).
|
|
||||||
*/
|
*/
|
||||||
CREATE OR REPLACE FUNCTION fn_kdasession_updateOnDelete()
|
CREATE OR REPLACE FUNCTION fn_kdasession_updateOnDelete()
|
||||||
RETURNS TRIGGER
|
RETURNS TRIGGER
|
||||||
|
|
@ -394,16 +572,18 @@ $$
|
||||||
DECLARE avatarId Int;
|
DECLARE avatarId Int;
|
||||||
DECLARE oldKills Int;
|
DECLARE oldKills Int;
|
||||||
DECLARE oldDeaths Int;
|
DECLARE oldDeaths Int;
|
||||||
DECLARE oldAssists Int;
|
DECLARE oldRevives Int;
|
||||||
|
DECLARE out integer;
|
||||||
BEGIN
|
BEGIN
|
||||||
avatarId := OLD.avatar_id;
|
avatarId := OLD.avatar_id;
|
||||||
oldKills := OLD.kills;
|
oldKills := OLD.kills;
|
||||||
oldDeaths := OLD.deaths;
|
oldDeaths := OLD.deaths;
|
||||||
oldAssists := OLD.assists;
|
oldRevives := OLD.Revives;
|
||||||
|
out := proc_kda_addEntryIfNone(avatarId);
|
||||||
UPDATE kda
|
UPDATE kda
|
||||||
SET kills = kills + oldKills,
|
SET kills = kills + oldKills,
|
||||||
deaths = deaths + oldDeaths,
|
deaths = deaths + oldDeaths,
|
||||||
assists = assists + oldAssists
|
revives = revives + oldRevives
|
||||||
WHERE avatar_id = avatarId;
|
WHERE avatar_id = avatarId;
|
||||||
RETURN OLD;
|
RETURN OLD;
|
||||||
END;
|
END;
|
||||||
|
|
@ -418,7 +598,6 @@ EXECUTE FUNCTION fn_kdasession_updateOnDelete();
|
||||||
/*
|
/*
|
||||||
Upon deletion of row entries for a character's session weapon stats,
|
Upon deletion of row entries for a character's session weapon stats,
|
||||||
the values are copied over to the campaign weapon stats record for that character.
|
the values are copied over to the campaign weapon stats record for that character.
|
||||||
This will fire mainly when called by the trigger for login (above).
|
|
||||||
*/
|
*/
|
||||||
CREATE OR REPLACE FUNCTION fn_weaponstatsession_updateOnDelete()
|
CREATE OR REPLACE FUNCTION fn_weaponstatsession_updateOnDelete()
|
||||||
RETURNS TRIGGER
|
RETURNS TRIGGER
|
||||||
|
|
@ -430,6 +609,7 @@ DECLARE oldKills Int;
|
||||||
DECLARE oldAssists Int;
|
DECLARE oldAssists Int;
|
||||||
DECLARE oldFired Int;
|
DECLARE oldFired Int;
|
||||||
DECLARE oldLanded Int;
|
DECLARE oldLanded Int;
|
||||||
|
DECLARE out integer;
|
||||||
BEGIN
|
BEGIN
|
||||||
avatarId := OLD.avatar_id;
|
avatarId := OLD.avatar_id;
|
||||||
weaponId := OLD.weapon_id;
|
weaponId := OLD.weapon_id;
|
||||||
|
|
@ -437,12 +617,14 @@ BEGIN
|
||||||
oldAssists := OLD.assists;
|
oldAssists := OLD.assists;
|
||||||
oldFired := OLD.shots_fired;
|
oldFired := OLD.shots_fired;
|
||||||
oldLanded := OLD.shots_landed;
|
oldLanded := OLD.shots_landed;
|
||||||
|
out := proc_weaponstat_addEntryIfNone(avatarId, weaponId);
|
||||||
UPDATE weaponstat
|
UPDATE weaponstat
|
||||||
SET kills = kills + oldKills,
|
SET kills = kills + oldKills,
|
||||||
assists = assists + oldAssists,
|
assists = assists + oldAssists,
|
||||||
shots_fired = shots_fired + oldFired,
|
shots_fired = shots_fired + oldFired,
|
||||||
shots_landed = shots_landed + oldLanded
|
shots_landed = shots_landed + oldLanded
|
||||||
WHERE avatar_id = avatarId AND weapon_id = weaponId;
|
WHERE avatar_id = avatarId AND weapon_id = weaponId;
|
||||||
|
RETURN OLD;
|
||||||
END;
|
END;
|
||||||
$$ LANGUAGE plpgsql;
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
|
@ -451,3 +633,152 @@ BEFORE DELETE
|
||||||
ON weaponstatsession
|
ON weaponstatsession
|
||||||
FOR EACH ROW
|
FOR EACH ROW
|
||||||
EXECUTE FUNCTION fn_weaponstatsession_updateOnDelete();
|
EXECUTE FUNCTION fn_weaponstatsession_updateOnDelete();
|
||||||
|
|
||||||
|
/*
|
||||||
|
Before inserting a value into the weaponstatsession table to session id -1,
|
||||||
|
correct the session id to the most current session id,
|
||||||
|
and invalidate the attempted insertion at that -1 session id.
|
||||||
|
*/
|
||||||
|
CREATE OR REPLACE FUNCTION fn_weaponstatsession_beforeInsert()
|
||||||
|
RETURNS TRIGGER
|
||||||
|
AS
|
||||||
|
$$
|
||||||
|
DECLARE avatarId Int;
|
||||||
|
DECLARE weaponId Int;
|
||||||
|
DECLARE sessionId Int;
|
||||||
|
DECLARE oldAssists Int;
|
||||||
|
DECLARE oldFired Int;
|
||||||
|
DECLARE oldLanded Int;
|
||||||
|
DECLARE out integer;
|
||||||
|
BEGIN
|
||||||
|
avatarId := NEW.avatar_id;
|
||||||
|
weaponId := NEW.weapon_id;
|
||||||
|
oldAssists := NEW.assists;
|
||||||
|
oldFired := NEW.shots_fired;
|
||||||
|
oldLanded := NEW.shots_landed;
|
||||||
|
sessionId := proc_sessionnumber_get(avatarId);
|
||||||
|
out := proc_weaponstatsession_addEntryIfNoneWithSessionId(avatarId, weaponId, sessionId);
|
||||||
|
BEGIN
|
||||||
|
UPDATE weaponstatsession
|
||||||
|
SET
|
||||||
|
assists = assists + oldAssists,
|
||||||
|
shots_fired = shots_fired + oldFired,
|
||||||
|
shots_landed = shots_landed + oldLanded
|
||||||
|
WHERE avatar_id = avatarId AND session_id = sessionId AND weapon_id = weaponId;
|
||||||
|
END;
|
||||||
|
RETURN NULL;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE OR REPLACE TRIGGER psf_weaponstatsession_beforeInsert
|
||||||
|
BEFORE INSERT
|
||||||
|
ON weaponstatsession
|
||||||
|
FOR EACH ROW
|
||||||
|
WHEN (NEW.session_id = -1)
|
||||||
|
EXECUTE FUNCTION fn_weaponstatsession_beforeInsert();
|
||||||
|
|
||||||
|
/*
|
||||||
|
A kill assist activity causes a major update to weapon stats:
|
||||||
|
the weapon that was used in the activity has the kills count for the killer updated/increased.
|
||||||
|
*/
|
||||||
|
CREATE OR REPLACE FUNCTION fn_assistactivity_updateRelatedStats()
|
||||||
|
RETURNS TRIGGER
|
||||||
|
AS
|
||||||
|
$$
|
||||||
|
DECLARE killerSessionId Int;
|
||||||
|
DECLARE killerId Int;
|
||||||
|
DECLARE weaponId Int;
|
||||||
|
DECLARE out integer;
|
||||||
|
BEGIN
|
||||||
|
killerId := NEW.killer_id;
|
||||||
|
weaponId := NEW.weapon_id;
|
||||||
|
killerSessionId := proc_sessionnumber_get(killerId);
|
||||||
|
out := proc_weaponstatsession_addEntryIfNone(killerId, killerSessionId, weaponId);
|
||||||
|
BEGIN
|
||||||
|
UPDATE weaponstatsession
|
||||||
|
SET assists = assists + 1
|
||||||
|
WHERE avatar_id = killerId AND session_id = killerSessionId AND weapon_id = weaponId;
|
||||||
|
END;
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE OR REPLACE TRIGGER psf_assistactivity_updateRelatedStats
|
||||||
|
AFTER INSERT
|
||||||
|
ON killactivity
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION fn_assistactivity_updateRelatedStats();
|
||||||
|
|
||||||
|
/*
|
||||||
|
Upon deletion of row entries for a character's building capture table,
|
||||||
|
the values are copied over to a total capture record for that character.
|
||||||
|
This will sort the values into one of two tables, and,
|
||||||
|
for one table, it will add to either one column or to a different column.
|
||||||
|
*/
|
||||||
|
CREATE OR REPLACE FUNCTION fn_buildingcapture_updateOnDelete()
|
||||||
|
RETURNS TRIGGER
|
||||||
|
AS
|
||||||
|
$$
|
||||||
|
DECLARE oldAvatarId Int;
|
||||||
|
DECLARE oldZoneId Int;
|
||||||
|
DECLARE oldBuildingId Int;
|
||||||
|
DECLARE oldExp Int;
|
||||||
|
DECLARE oldType CHAR(3);
|
||||||
|
DECLARE out integer;
|
||||||
|
BEGIN
|
||||||
|
oldAvatarId := NEW.avatar_id;
|
||||||
|
oldZoneId := NEW.zone_id;
|
||||||
|
oldBuildingId := NEW.building_id;
|
||||||
|
oldExp := NEW.exp;
|
||||||
|
oldType := NEW.exp_type;
|
||||||
|
BEGIN
|
||||||
|
IF (oldType LIKE "bep") THEN
|
||||||
|
out := proc_expbuildingcapture_addEntryIfNone(avatarId, buildingId, zoneId);
|
||||||
|
UPDATE expbuildingcapture
|
||||||
|
SET bep = bep + oldExp
|
||||||
|
WHERE avatar_id = oldAvatarId AND zone_id = oldZOneId AND building_id = oldBuildingId;
|
||||||
|
ELSIF (oldType LIKE "cep") THEN
|
||||||
|
out := proc_expbuildingcapture_addEntryIfNone(avatarId, buildingId, zoneId);
|
||||||
|
UPDATE expbuildingcapture
|
||||||
|
SET cep = cep + oldExp
|
||||||
|
WHERE avatar_id = oldAvatarId AND zone_id = oldZOneId AND building_id = oldBuildingId;
|
||||||
|
ELSIF (oldType LIKE "llu") THEN
|
||||||
|
out := proc_llubuildingcapture_addEntryIfNone(avatarId, buildingId, zoneId);
|
||||||
|
UPDATE llubuildingcapture
|
||||||
|
SET exp = exp + oldExp
|
||||||
|
WHERE avatar_id = oldAvatarId AND zone_id = oldZOneId AND building_id = oldBuildingId;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE OR REPLACE TRIGGER psf_buildingcapture_updateOnDelete
|
||||||
|
BEFORE DELETE
|
||||||
|
ON buildingcapture
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION fn_buildingcapture_updateOnDelete();
|
||||||
|
|
||||||
|
/*
|
||||||
|
If a new avatar is created, a corresponding progression debt entry is also created by default.
|
||||||
|
*/
|
||||||
|
CREATE OR REPLACE FUNCTION fn_avatar_addDebtEntry()
|
||||||
|
RETURNS TRIGGER
|
||||||
|
AS
|
||||||
|
$$
|
||||||
|
DECLARE newAvatarId Int;
|
||||||
|
BEGIN
|
||||||
|
newAvatarId := NEW.avatar_id;
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO progressiondebt (avatar_id)
|
||||||
|
VALUES (newAvatarId);
|
||||||
|
END;
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE OR REPLACE TRIGGER psf_avatar_addDebtEntry
|
||||||
|
AFTER INSERT
|
||||||
|
ON avatar
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION fn_avatar_addDebtEntry();
|
||||||
|
|
|
||||||
|
|
@ -1,380 +0,0 @@
|
||||||
/* changes to objects from V008__Scoring.sql */
|
|
||||||
ALTER TABLE killactivity
|
|
||||||
ADD COLUMN victim_mounted INT NOT NULL DEFAULT 0;
|
|
||||||
|
|
||||||
DROP PROCEDURE IF EXISTS proc_sessionnumber_initAndOrIncrease;
|
|
||||||
|
|
||||||
CREATE OR REPLACE PROCEDURE proc_sessionnumber_initAndOrIncreasePerHour
|
|
||||||
(avatarId IN Int, number OUT Int, nextNumber OUT Int)
|
|
||||||
AS
|
|
||||||
$$
|
|
||||||
DECLARE time TIMESTAMP;
|
|
||||||
BEGIN
|
|
||||||
SELECT MAX(session_id) INTO number
|
|
||||||
FROM sessionnumber
|
|
||||||
WHERE avatar_id = avatarId;
|
|
||||||
SELECT COALESCE(timestamp) INTO time
|
|
||||||
FROM sessionnumber
|
|
||||||
WHERE avatar_id = avatarId AND session_id = number;
|
|
||||||
IF (time IS null) THEN
|
|
||||||
number := 0;
|
|
||||||
nextNumber := 1;
|
|
||||||
INSERT INTO sessionnumber
|
|
||||||
VALUES (avatarId, nextNumber);
|
|
||||||
ELSIF (CURRENT_TIMESTAMP > DATE_TRUNC('hour', time) + interval '1' hour) THEN
|
|
||||||
nextNumber := number + 1;
|
|
||||||
INSERT INTO sessionnumber
|
|
||||||
VALUES (avatarId, nextNumber);
|
|
||||||
ELSE
|
|
||||||
nextNumber := number;
|
|
||||||
UPDATE sessionnumber
|
|
||||||
SET timestamp = CURRENT_TIMESTAMP
|
|
||||||
WHERE avatar_id = avatarId AND session_id = number;
|
|
||||||
END IF;
|
|
||||||
END;
|
|
||||||
$$ LANGUAGE plpgsql;
|
|
||||||
|
|
||||||
CREATE OR REPLACE PROCEDURE proc_sessionnumber_get
|
|
||||||
(avatarId IN Int, number OUT Int)
|
|
||||||
AS
|
|
||||||
$$
|
|
||||||
DECLARE sessionId Int;
|
|
||||||
BEGIN
|
|
||||||
SELECT COALESCE(session_id) INTO sessionId
|
|
||||||
FROM account
|
|
||||||
WHERE avatar_logged_in = avatarId;
|
|
||||||
IF sessionId IS NULL THEN
|
|
||||||
number := 0;
|
|
||||||
ELSE
|
|
||||||
number := sessionId;
|
|
||||||
END IF;
|
|
||||||
END;
|
|
||||||
$$ LANGUAGE plpgsql;
|
|
||||||
|
|
||||||
CREATE OR REPLACE PROCEDURE proc_weaponstat_addEntryIfNone
|
|
||||||
(avatarId IN Int, weaponId IN Int)
|
|
||||||
AS
|
|
||||||
$$
|
|
||||||
BEGIN
|
|
||||||
IF NOT EXISTS(
|
|
||||||
SELECT avatar_id, weapon_id
|
|
||||||
FROM weaponstat
|
|
||||||
WHERE avatar_id = avatarId AND weapon_id = weaponId
|
|
||||||
GROUP BY avatar_id, weapon_id
|
|
||||||
) THEN
|
|
||||||
INSERT INTO weaponstat (avatar_id, weapon_id)
|
|
||||||
VALUES (avatarId, weaponId);
|
|
||||||
END IF;
|
|
||||||
END;
|
|
||||||
$$ LANGUAGE plpgsql;
|
|
||||||
|
|
||||||
CREATE OR REPLACE PROCEDURE proc_weaponstatsession_addEntryIfNoneWithSessionId
|
|
||||||
(avatarId IN Int, sessionId IN Int, weaponId IN Int)
|
|
||||||
AS
|
|
||||||
$$
|
|
||||||
BEGIN
|
|
||||||
IF NOT EXISTS(
|
|
||||||
SELECT avatar_id, session_id, weapon_id
|
|
||||||
FROM weaponstatsession
|
|
||||||
WHERE avatar_id = avatarId AND weapon_id = weaponId AND session_id = sessionId
|
|
||||||
GROUP BY avatar_id, session_id, weapon_id
|
|
||||||
) THEN
|
|
||||||
INSERT INTO weaponstatsession (avatar_id, session_id, weapon_id)
|
|
||||||
VALUES (avatarId, sessionId, weaponId);
|
|
||||||
END IF;
|
|
||||||
END;
|
|
||||||
$$ LANGUAGE plpgsql;
|
|
||||||
|
|
||||||
CREATE OR REPLACE PROCEDURE proc_weaponstatsession_addEntryIfNone
|
|
||||||
(avatarId IN Int, weaponId IN Int)
|
|
||||||
AS
|
|
||||||
$$
|
|
||||||
DECLARE sessionId Int;
|
|
||||||
BEGIN
|
|
||||||
CALL proc_sessionnumber_get(avatarId, sessionId);
|
|
||||||
CALL proc_weaponstatsession_addEntryIfNoneWithSessionId(avatarId, sessionId, weaponId);
|
|
||||||
END;
|
|
||||||
$$ LANGUAGE plpgsql;
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION fn_account_newSession()
|
|
||||||
RETURNS TRIGGER
|
|
||||||
AS
|
|
||||||
$$
|
|
||||||
DECLARE avatarId Int;
|
|
||||||
DECLARE sessionId Int;
|
|
||||||
DECLARE oldSessionId Int;
|
|
||||||
BEGIN
|
|
||||||
avatarId := NEW.avatar_logged_in;
|
|
||||||
CALL proc_sessionnumber_initAndOrIncreasePerHour(avatarId, oldSessionId, sessionId);
|
|
||||||
IF (sessionId > oldSessionId) THEN
|
|
||||||
BEGIN
|
|
||||||
UPDATE account
|
|
||||||
SET session_id = sessionId
|
|
||||||
WHERE id = OLD.id;
|
|
||||||
INSERT INTO kdasession (avatar_id, session_id)
|
|
||||||
VALUES (avatarId, sessionId);
|
|
||||||
END;
|
|
||||||
END IF;
|
|
||||||
RETURN NEW;
|
|
||||||
END;
|
|
||||||
$$ LANGUAGE plpgsql;
|
|
||||||
|
|
||||||
CREATE OR REPLACE TRIGGER psf_account_newSession
|
|
||||||
AFTER UPDATE
|
|
||||||
OF avatar_logged_in
|
|
||||||
ON account
|
|
||||||
FOR EACH ROW
|
|
||||||
WHEN (OLD.avatar_logged_in = 0 AND NEW.avatar_logged_in > 0)
|
|
||||||
EXECUTE FUNCTION fn_account_newSession();
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION fn_killactivity_updateRelatedStats()
|
|
||||||
RETURNS TRIGGER
|
|
||||||
AS
|
|
||||||
$$
|
|
||||||
DECLARE killerSessionId Int;
|
|
||||||
DECLARE victimSessionId Int;
|
|
||||||
DECLARE killerId Int;
|
|
||||||
DECLARE victimId Int;
|
|
||||||
DECLARE weaponId Int;
|
|
||||||
BEGIN
|
|
||||||
killerId := NEW.killer_id;
|
|
||||||
victimId := NEW.victim_id;
|
|
||||||
weaponId := NEW.weapon_id;
|
|
||||||
CALL proc_sessionnumber_get(killerId, killerSessionId);
|
|
||||||
CALL proc_sessionnumber_get(victimId, victimSessionId);
|
|
||||||
BEGIN
|
|
||||||
UPDATE kdasession
|
|
||||||
SET kills = kills + 1
|
|
||||||
WHERE avatar_id = killerId AND session_id = killerSessionId;
|
|
||||||
END;
|
|
||||||
BEGIN
|
|
||||||
UPDATE kdasession
|
|
||||||
SET deaths = deaths + 1
|
|
||||||
WHERE avatar_id = victimId AND session_id = victimSessionId;
|
|
||||||
END;
|
|
||||||
BEGIN
|
|
||||||
CALL proc_weaponstatsession_addEntryIfNoneWithSessionId(killerId, killerSessionId, weaponId);
|
|
||||||
UPDATE weaponstatsession
|
|
||||||
SET kills = kills + 1
|
|
||||||
WHERE avatar_id = killerId AND session_id = killerSessionId AND weapon_id = weaponId;
|
|
||||||
END;
|
|
||||||
RETURN NEW;
|
|
||||||
END;
|
|
||||||
$$ LANGUAGE plpgsql;
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION fn_weaponstatsession_addEntryIfNone()
|
|
||||||
RETURNS TRIGGER
|
|
||||||
AS
|
|
||||||
$$
|
|
||||||
DECLARE avatarId Int;
|
|
||||||
DECLARE weaponId Int;
|
|
||||||
BEGIN
|
|
||||||
avatarId := NEW.avatar_id;
|
|
||||||
weaponId := NEW.weapon_id;
|
|
||||||
CALL proc_weaponstatsession_addEntryIfNone(avatarId, weaponId);
|
|
||||||
RETURN NEW;
|
|
||||||
END;
|
|
||||||
$$ LANGUAGE plpgsql;
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION fn_kda_addEntryIfNone()
|
|
||||||
RETURNS TRIGGER
|
|
||||||
AS
|
|
||||||
$$
|
|
||||||
DECLARE avatarId Int;
|
|
||||||
BEGIN
|
|
||||||
avatarId := NEW.avatar_id;
|
|
||||||
CALL proc_kda_addEntryIfNone(avatarId);
|
|
||||||
RETURN NEW;
|
|
||||||
END;
|
|
||||||
$$ LANGUAGE plpgsql;
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION fn_weaponstat_addEntryIfNone()
|
|
||||||
RETURNS TRIGGER
|
|
||||||
AS
|
|
||||||
$$
|
|
||||||
DECLARE avatarId Int;
|
|
||||||
DECLARE weaponId Int;
|
|
||||||
BEGIN
|
|
||||||
avatarId := NEW.avatar_id;
|
|
||||||
weaponId := NEW.weapon_id;
|
|
||||||
CALL proc_weaponstat_addEntryIfNone(avatarId, weaponId);
|
|
||||||
RETURN NEW;
|
|
||||||
END;
|
|
||||||
$$ LANGUAGE plpgsql;
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION fn_kdasession_updateOnDelete()
|
|
||||||
RETURNS TRIGGER
|
|
||||||
AS
|
|
||||||
$$
|
|
||||||
DECLARE avatarId Int;
|
|
||||||
DECLARE oldKills Int;
|
|
||||||
DECLARE oldDeaths Int;
|
|
||||||
DECLARE oldAssists Int;
|
|
||||||
BEGIN
|
|
||||||
avatarId := OLD.avatar_id;
|
|
||||||
oldKills := OLD.kills;
|
|
||||||
oldDeaths := OLD.deaths;
|
|
||||||
oldAssists := OLD.assists;
|
|
||||||
CALL proc_kda_addEntryIfNone(avatarId);
|
|
||||||
UPDATE kda
|
|
||||||
SET kills = kills + oldKills,
|
|
||||||
deaths = deaths + oldDeaths,
|
|
||||||
assists = assists + oldAssists
|
|
||||||
WHERE avatar_id = avatarId;
|
|
||||||
RETURN OLD;
|
|
||||||
END;
|
|
||||||
$$ LANGUAGE plpgsql;
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION fn_weaponstatsession_updateOnDelete()
|
|
||||||
RETURNS TRIGGER
|
|
||||||
AS
|
|
||||||
$$
|
|
||||||
DECLARE avatarId Int;
|
|
||||||
DECLARE weaponId Int;
|
|
||||||
DECLARE oldKills Int;
|
|
||||||
DECLARE oldAssists Int;
|
|
||||||
DECLARE oldFired Int;
|
|
||||||
DECLARE oldLanded Int;
|
|
||||||
BEGIN
|
|
||||||
avatarId := OLD.avatar_id;
|
|
||||||
weaponId := OLD.weapon_id;
|
|
||||||
oldKills := OLD.kills;
|
|
||||||
oldAssists := OLD.assists;
|
|
||||||
oldFired := OLD.shots_fired;
|
|
||||||
oldLanded := OLD.shots_landed;
|
|
||||||
CALL proc_weaponstat_addEntryIfNone(avatarId, weaponId);
|
|
||||||
UPDATE weaponstat
|
|
||||||
SET kills = kills + oldKills,
|
|
||||||
assists = assists + oldAssists,
|
|
||||||
shots_fired = shots_fired + oldFired,
|
|
||||||
shots_landed = shots_landed + oldLanded
|
|
||||||
WHERE avatar_id = avatarId AND weapon_id = weaponId;
|
|
||||||
RETURN OLD;
|
|
||||||
END;
|
|
||||||
$$ LANGUAGE plpgsql;
|
|
||||||
|
|
||||||
/* new objects */
|
|
||||||
CREATE TABLE IF NOT EXISTS machinedestroyed (
|
|
||||||
"index" SERIAL PRIMARY KEY NOT NULL,
|
|
||||||
"avatar_id" INT NOT NULL REFERENCES avatar (id),
|
|
||||||
"weapon_id" INT NOT NULL,
|
|
||||||
"machine_type" INT NOT NULL,
|
|
||||||
"machine_faction" SMALLINT NOT NULL,
|
|
||||||
"hacked_faction" SMALLINT NOT NULL,
|
|
||||||
"as_cargo" BOOLEAN NOT NULL,
|
|
||||||
"zone_num" SMALLINT NOT NULL,
|
|
||||||
"px" INT NOT NULL,
|
|
||||||
"py" INT NOT NULL,
|
|
||||||
"pz" INT NOT NULL,
|
|
||||||
"timestamp" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS "assistactivity" (
|
|
||||||
"index" SERIAL PRIMARY KEY NOT NULL,
|
|
||||||
"victim_id" INT NOT NULL REFERENCES avatar (id),
|
|
||||||
"attacker_id" INT NOT NULL REFERENCES avatar (id),
|
|
||||||
"weapon_id" SMALLINT NOT NULL,
|
|
||||||
"zone_id" SMALLINT NOT NULL,
|
|
||||||
"px" INT NOT NULL,
|
|
||||||
"py" INT NOT NULL,
|
|
||||||
"pz" INT NOT NULL,
|
|
||||||
"exp" INT NOT NULL,
|
|
||||||
"timestamp" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS supportactivity (
|
|
||||||
"index" SERIAL PRIMARY KEY NOT NULL,
|
|
||||||
"user_id" INT NOT NULL REFERENCES avatar (id), -- player that provides the support
|
|
||||||
"target_id" INT NOT NULL REFERENCES avatar (id), -- benefactor of the support
|
|
||||||
"target_exosuit" SMALLINT NOT NULL, -- benefactor's exo-suit
|
|
||||||
"interaction_type" SMALLINT NOT NULL, -- classification of support
|
|
||||||
"intermediate_type" INT DEFAULT 0, -- through what medium user_id supports target_id
|
|
||||||
"implement_type" INT DEFAULT 0, -- tool utilized by user_id to support target_id, potentially via interaction with intermediate_type
|
|
||||||
"exp" INT NOT NULL,
|
|
||||||
"timestamp" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS respawnsession (
|
|
||||||
"avatar_id" INT NOT NULL REFERENCES avatar (id),
|
|
||||||
"session_id" INT NOT NULL,
|
|
||||||
"respawn_count" INT NOT NULL DEFAULT 0,
|
|
||||||
UNIQUE(avatar_id, session_id)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS respawn (
|
|
||||||
"avatar_id" INT NOT NULL REFERENCES avatar (id),
|
|
||||||
"respawn_count" INT NOT NULL DEFAULT 0,
|
|
||||||
UNIQUE(avatar_id)
|
|
||||||
);
|
|
||||||
|
|
||||||
/*
|
|
||||||
Before inserting a value into the weaponstatsession table to session id -1,
|
|
||||||
correct the session id to the most current session id,
|
|
||||||
and invalidate the attempted insertion at that -1 session id.
|
|
||||||
*/
|
|
||||||
CREATE OR REPLACE FUNCTION fn_weaponstatsession_beforeInsert()
|
|
||||||
RETURNS TRIGGER
|
|
||||||
AS
|
|
||||||
$$
|
|
||||||
DECLARE avatarId Int;
|
|
||||||
DECLARE weaponId Int;
|
|
||||||
DECLARE sessionId Int;
|
|
||||||
DECLARE oldAssists Int;
|
|
||||||
DECLARE oldFired Int;
|
|
||||||
DECLARE oldLanded Int;
|
|
||||||
BEGIN
|
|
||||||
avatarId := NEW.avatar_id;
|
|
||||||
weaponId := NEW.weapon_id;
|
|
||||||
oldAssists := NEW.assists;
|
|
||||||
oldFired := NEW.shots_fired;
|
|
||||||
oldLanded := NEW.shots_landed;
|
|
||||||
CALL proc_sessionnumber_get(avatarId, sessionId);
|
|
||||||
CALL proc_weaponstatsession_addEntryIfNoneWithSessionId(avatarId, sessionId, weaponId);
|
|
||||||
BEGIN
|
|
||||||
UPDATE weaponstatsession
|
|
||||||
SET
|
|
||||||
assists = assists + oldAssists,
|
|
||||||
shots_fired = shots_fired + oldFired,
|
|
||||||
shots_landed = shots_landed + oldLanded
|
|
||||||
WHERE avatar_id = avatarId AND session_id = sessionId AND weapon_id = weaponId;
|
|
||||||
END;
|
|
||||||
RETURN NULL;
|
|
||||||
END;
|
|
||||||
$$ LANGUAGE plpgsql;
|
|
||||||
|
|
||||||
CREATE OR REPLACE TRIGGER psf_weaponstatsession_beforeInsert
|
|
||||||
BEFORE INSERT
|
|
||||||
ON weaponstatsession
|
|
||||||
FOR EACH ROW
|
|
||||||
WHEN (NEW.session_id = -1)
|
|
||||||
EXECUTE FUNCTION fn_weaponstatsession_beforeInsert();
|
|
||||||
|
|
||||||
/*
|
|
||||||
A kill assist activity causes a major update to weapon stats:
|
|
||||||
the weapon that was used in the activity has the kills count for the killer updated/increased.
|
|
||||||
*/
|
|
||||||
CREATE OR REPLACE FUNCTION fn_assistactivity_updateRelatedStats()
|
|
||||||
RETURNS TRIGGER
|
|
||||||
AS
|
|
||||||
$$
|
|
||||||
DECLARE killerSessionId Int;
|
|
||||||
DECLARE killerId Int;
|
|
||||||
DECLARE weaponId Int;
|
|
||||||
BEGIN
|
|
||||||
killerId := NEW.killer_id;
|
|
||||||
weaponId := NEW.weapon_id;
|
|
||||||
CALL proc_sessionnumber_get(killerId, killerSessionId);
|
|
||||||
BEGIN
|
|
||||||
UPDATE weaponstatsession
|
|
||||||
SET assists = assists + 1
|
|
||||||
WHERE avatar_id = killerId AND session_id = killerSessionId AND weapon_id = weaponId;
|
|
||||||
END;
|
|
||||||
RETURN NEW;
|
|
||||||
END;
|
|
||||||
$$ LANGUAGE plpgsql;
|
|
||||||
|
|
||||||
CREATE OR REPLACE TRIGGER psf_assistactivity_updateRelatedStats
|
|
||||||
AFTER INSERT
|
|
||||||
ON killactivity
|
|
||||||
FOR EACH ROW
|
|
||||||
EXECUTE FUNCTION fn_assistactivity_updateRelatedStats();
|
|
||||||
|
|
@ -231,17 +231,17 @@ object VehicleSpawnPadControlTest {
|
||||||
override def SetupNumberPools(): Unit = {}
|
override def SetupNumberPools(): Unit = {}
|
||||||
}
|
}
|
||||||
zone.GUID(guid)
|
zone.GUID(guid)
|
||||||
zone.actor = system.spawn(ZoneActor(zone), s"test-zone-${System.nanoTime()}")
|
zone.actor = system.spawn(ZoneActor(zone), s"test-zone-${System.currentTimeMillis()}")
|
||||||
|
|
||||||
// Hack: Wait for the Zone to finish booting, otherwise later tests will fail randomly due to race conditions
|
// Hack: Wait for the Zone to finish booting, otherwise later tests will fail randomly due to race conditions
|
||||||
// with actor probe setting
|
// with actor probe setting
|
||||||
// TODO(chord): Remove when Zone supports notification of booting being complete
|
// TODO(chord): Remove when Zone supports notification of booting being complete
|
||||||
Thread.sleep(5000)
|
Thread.sleep(5000)
|
||||||
|
|
||||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), s"vehicle-control-${System.nanoTime()}")
|
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), s"vehicle-control-${System.currentTimeMillis()}")
|
||||||
|
|
||||||
val pad = VehicleSpawnPad(GlobalDefinitions.mb_pad_creation)
|
val pad = VehicleSpawnPad(GlobalDefinitions.mb_pad_creation)
|
||||||
pad.Actor = system.actorOf(Props(classOf[VehicleSpawnControl], pad), s"test-pad-${System.nanoTime()}")
|
pad.Actor = system.actorOf(Props(classOf[VehicleSpawnControl], pad), s"test-pad-${System.currentTimeMillis()}")
|
||||||
pad.Owner =
|
pad.Owner =
|
||||||
new Building("Building", building_guid = 0, map_id = 0, zone, StructureType.Building, GlobalDefinitions.building)
|
new Building("Building", building_guid = 0, map_id = 0, zone, StructureType.Building, GlobalDefinitions.building)
|
||||||
pad.Owner.Faction = faction
|
pad.Owner.Faction = faction
|
||||||
|
|
|
||||||
|
|
@ -75,15 +75,6 @@ game {
|
||||||
third-party = no
|
third-party = no
|
||||||
}
|
}
|
||||||
|
|
||||||
# Battle experience rate
|
|
||||||
bep-rate = 1.0
|
|
||||||
|
|
||||||
# Command experience rate
|
|
||||||
cep-rate = 1.0
|
|
||||||
|
|
||||||
# The maximum command experience that can be earned in a facility capture based on squad size
|
|
||||||
maximum-cep-per-squad-size = [990, 1980, 3466, 4950, 6436, 7920, 9406, 10890, 12376, 13860]
|
|
||||||
|
|
||||||
# Modify the amount of mending per autorepair tick for facility amenities
|
# Modify the amount of mending per autorepair tick for facility amenities
|
||||||
amenity-autorepair-rate = 1.0
|
amenity-autorepair-rate = 1.0
|
||||||
|
|
||||||
|
|
@ -226,6 +217,163 @@ game {
|
||||||
|
|
||||||
# Don't ask.
|
# Don't ask.
|
||||||
doors-can-be-opened-by-med-app-from-this-distance = 5.05
|
doors-can-be-opened-by-med-app-from-this-distance = 5.05
|
||||||
|
|
||||||
|
# How the experience calculates
|
||||||
|
experience {
|
||||||
|
# The short contribution time when events are collected and evaluated.
|
||||||
|
short-contribution-time = 300000
|
||||||
|
# The long contribution time when events are collected and evaluated
|
||||||
|
# even factoring the same events from the short contribution time.
|
||||||
|
# As a result, when comparing the two event lists, similar actors may appear
|
||||||
|
# but their contributions may be different.
|
||||||
|
long-contribution-time = 600000
|
||||||
|
|
||||||
|
# Battle experience points
|
||||||
|
# BEP is to be calculated in relation to how valuable a kill is worth.
|
||||||
|
bep = {
|
||||||
|
# After all calculations are complete, multiple the result by this value
|
||||||
|
rate = 1.0
|
||||||
|
# These numbers are to determine the starting value for a particular kill
|
||||||
|
base = {
|
||||||
|
# Black Ops multiplies the base value by this much
|
||||||
|
bops-multiplier = 10.0
|
||||||
|
# If the player who died ever utilized a mechanized assault exo-suit
|
||||||
|
as-max = 250
|
||||||
|
# The player who died got at least one kill
|
||||||
|
with-kills = 100
|
||||||
|
# The player who died was mounted in a vehicle at the time of death
|
||||||
|
as-mounted = 100
|
||||||
|
# The player who died after having been in the game world for a while after spawning.
|
||||||
|
# Dying before this is often called a "spawn kill".
|
||||||
|
mature = 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Support experience points
|
||||||
|
# The events from which support experience rises are numerous.
|
||||||
|
# Calculation is determined by the selection of an "event" that decides how the values are combined.
|
||||||
|
sep = {
|
||||||
|
# After all calculations are complete, multiple the result by this value
|
||||||
|
rate = 1.0
|
||||||
|
# When using an advanced nanite transport to deposit into the resource silo of a major facility,
|
||||||
|
# for reaching the maximum amount of a single deposit,
|
||||||
|
# reward the user with this amount of support experience points.
|
||||||
|
# Small deposits reward only a percentage of this value.
|
||||||
|
ntu-silo-deposit-reward = 100
|
||||||
|
# When the event can not be found, this flat sum is rewarded.
|
||||||
|
# This should not be treated as a feature.
|
||||||
|
# It is a bug.
|
||||||
|
# Check your event label calls.
|
||||||
|
can-not-find-event-default-value = 15
|
||||||
|
# The events by which support experience calculation occurs.
|
||||||
|
# Events can be composed of three parts: a base value, a per-use (shots) value, and an active amount value.
|
||||||
|
# "Per-use" relies on knowledge from the server about the number of times this exact action occurred before the event.
|
||||||
|
# "Active amount" relies on knowledge from the server about how much of the changes for this event are still valid.
|
||||||
|
# Some changes can be undone by other events or other behavior.
|
||||||
|
#
|
||||||
|
# name - label by which this event is organized
|
||||||
|
# base - whole number value
|
||||||
|
# shots-multiplier - whether use count matters for this event
|
||||||
|
# - when set to 0.0 (default), it does not
|
||||||
|
# shots-limit - upper limit of use count
|
||||||
|
# - cap the count here, if higher
|
||||||
|
# shots-cutoff - if the use count exceeds this number, the event no longer applies
|
||||||
|
# - a hard limit that should zero the contribution reward
|
||||||
|
# - the *-cutoff should probably apply before *-limit, maybe
|
||||||
|
# shots-nat-log - when set, may the use count to a natural logarithmic curve
|
||||||
|
# - actually the exponent on the use count before the logarithm
|
||||||
|
# - similar to shots-limit, but the curve plateaus quickly
|
||||||
|
# amount-multiplier - whether active amount matters for this event
|
||||||
|
# - when set to 0.0 (default), it does not
|
||||||
|
events = [
|
||||||
|
{
|
||||||
|
name = "support-heal"
|
||||||
|
base = 10
|
||||||
|
shots-multiplier = 5.0
|
||||||
|
shots-limit = 100
|
||||||
|
amount-multiplier = 2.0
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "support-repair"
|
||||||
|
base = 10
|
||||||
|
shots-multiplier = 5.0
|
||||||
|
shots-limit = 100
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "support-repair-terminal"
|
||||||
|
base = 10
|
||||||
|
shots-multiplier = 5.0
|
||||||
|
shots-limit = 100
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "support-repair-turret"
|
||||||
|
base = 10
|
||||||
|
shots-multiplier = 5.0
|
||||||
|
shots-limit = 100
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "mounted-kill"
|
||||||
|
base = 25
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "router"
|
||||||
|
base = 15
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "hotdrop"
|
||||||
|
base = 25
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "hack"
|
||||||
|
base = 5
|
||||||
|
amount-multiplier = 5.0
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "ams-resupply"
|
||||||
|
base = 15
|
||||||
|
shots-multiplier = 1.0
|
||||||
|
shots-nat-log = 5.0
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "lodestar-repair"
|
||||||
|
base = 10
|
||||||
|
shots-multiplier = 1.0
|
||||||
|
shots-nat-log = 5.0
|
||||||
|
shots-limit = 100
|
||||||
|
amount-multiplier = 1.0
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "lodestar-rearm"
|
||||||
|
base = 10
|
||||||
|
shots-multiplier = 1.0
|
||||||
|
shots-nat-log = 5.0
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "revival"
|
||||||
|
base = 0
|
||||||
|
shots-multiplier = 15.0
|
||||||
|
shots-nat-log = 5.0
|
||||||
|
shots-cutoff = 10
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Support experience points
|
||||||
|
cep = {
|
||||||
|
# After all calculations are complete, multiple the result by this value
|
||||||
|
rate = 1.0
|
||||||
|
# When command experience points are rewarded to the lattice link unit carrier,
|
||||||
|
# modify the original value by this modifier.
|
||||||
|
llu-carrier-modifier = 0.5
|
||||||
|
# The maximum command experience that can be earned in a facility capture based on squad size
|
||||||
|
maximum-per-squad-size = [990, 1980, 3466, 4950, 6436, 7920, 9406, 10890, 12376, 13860]
|
||||||
|
# When the cep has to be capped for squad size, add a small value to the capped value
|
||||||
|
# -1 reuses the cep before being capped
|
||||||
|
squad-size-limit-overflow = -1
|
||||||
|
# When the cep has to be capped for squad size, calculate a small amount to add to the capped value
|
||||||
|
squad-size-limit-overflow-multiplier = 0.2
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
anti-cheat {
|
anti-cheat {
|
||||||
|
|
|
||||||
|
|
@ -851,12 +851,18 @@ object AvatarActor {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
def updateToolDischargeFor(avatarId: Long, lives: Seq[Life]): Unit = {
|
def updateToolDischargeFor(avatar: Avatar): Unit = {
|
||||||
lives
|
updateToolDischargeFor(avatar.id, avatar.scorecard.CurrentLife)
|
||||||
.flatMap { _.equipmentStats }
|
}
|
||||||
.foreach { stat =>
|
|
||||||
zones.exp.ToDatabase.reportToolDischarge(avatarId, stat)
|
def updateToolDischargeFor(avatarId: Long, life: Life): Unit = {
|
||||||
}
|
updateToolDischargeFor(avatarId, life.equipmentStats)
|
||||||
|
}
|
||||||
|
|
||||||
|
def updateToolDischargeFor(avatarId: Long, stats: Seq[EquipmentStat]): Unit = {
|
||||||
|
stats.foreach { stat =>
|
||||||
|
zones.exp.ToDatabase.reportToolDischarge(avatarId, stat)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def toAvatar(avatar: persistence.Avatar): Avatar = {
|
def toAvatar(avatar: persistence.Avatar): Avatar = {
|
||||||
|
|
@ -1091,7 +1097,6 @@ class AvatarActor(
|
||||||
.receiveSignal {
|
.receiveSignal {
|
||||||
case (_, PostStop) =>
|
case (_, PostStop) =>
|
||||||
AvatarActor.avatarNoLongerLoggedIn(account.id)
|
AvatarActor.avatarNoLongerLoggedIn(account.id)
|
||||||
AvatarActor.updateToolDischargeFor(avatar.id.toLong, avatar.scorecard.CurrentLife +: avatar.scorecard.Lives)
|
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1863,6 +1868,7 @@ class AvatarActor(
|
||||||
AvatarActor.setBepOnly(avatar.id, avatar.bep + supportExperiencePool)
|
AvatarActor.setBepOnly(avatar.id, avatar.bep + supportExperiencePool)
|
||||||
}
|
}
|
||||||
saveLockerFunc()
|
saveLockerFunc()
|
||||||
|
AvatarActor.updateToolDischargeFor(avatar)
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3084,6 +3090,7 @@ class AvatarActor(
|
||||||
}
|
}
|
||||||
|
|
||||||
def updateDeaths(deathStat: Death): Unit = {
|
def updateDeaths(deathStat: Death): Unit = {
|
||||||
|
AvatarActor.updateToolDischargeFor(avatar)
|
||||||
avatar.scorecard.rate(deathStat)
|
avatar.scorecard.rate(deathStat)
|
||||||
val _session = session.get
|
val _session = session.get
|
||||||
val zone = _session.zone
|
val zone = _session.zone
|
||||||
|
|
@ -3138,13 +3145,13 @@ class AvatarActor(
|
||||||
def updateExperienceAndType(exp: Long): (Long, ExperienceType) = {
|
def updateExperienceAndType(exp: Long): (Long, ExperienceType) = {
|
||||||
val _session = session.get
|
val _session = session.get
|
||||||
val player = _session.player
|
val player = _session.player
|
||||||
val gameOpts = Config.app.game
|
val gameOpts = Config.app.game.experience.bep
|
||||||
val (modifier, msg) = if (player.Carrying.contains(SpecialCarry.RabbitBall)) {
|
val (modifier, msg) = if (player.Carrying.contains(SpecialCarry.RabbitBall)) {
|
||||||
(1.25f, ExperienceType.RabbitBall)
|
(1.25f, ExperienceType.RabbitBall)
|
||||||
} else {
|
} else {
|
||||||
(1f, ExperienceType.Normal)
|
(1f, ExperienceType.Normal)
|
||||||
}
|
}
|
||||||
((exp * modifier * gameOpts.bepRate).toLong, msg)
|
((exp * modifier * gameOpts.rate).toLong, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
def updateToolDischarge(stats: EquipmentStat): Unit = {
|
def updateToolDischarge(stats: EquipmentStat): Unit = {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import akka.actor.typed.scaladsl.adapter._
|
||||||
import akka.actor.{ActorContext, typed}
|
import akka.actor.{ActorContext, typed}
|
||||||
import net.psforever.packet.game.objectcreate.ConstructorData
|
import net.psforever.packet.game.objectcreate.ConstructorData
|
||||||
import net.psforever.services.Service
|
import net.psforever.services.Service
|
||||||
|
import net.psforever.objects.zones.exp
|
||||||
|
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
|
|
@ -225,8 +226,6 @@ class SessionAvatarHandlers(
|
||||||
case AvatarResponse.DestroyDisplay(killer, victim, method, unk)
|
case AvatarResponse.DestroyDisplay(killer, victim, method, unk)
|
||||||
if killer.CharId == avatar.id && killer.Faction != victim.Faction =>
|
if killer.CharId == avatar.id && killer.Faction != victim.Faction =>
|
||||||
sendResponse(sessionData.destroyDisplayMessage(killer, victim, method, unk))
|
sendResponse(sessionData.destroyDisplayMessage(killer, victim, method, unk))
|
||||||
//TODO Temporary thing that should go somewhere else and use proper xp values
|
|
||||||
// avatarActor ! AvatarActor.AwardCep((100 * Config.app.game.cepRate).toLong)
|
|
||||||
|
|
||||||
case AvatarResponse.Destroy(victim, killer, weapon, pos) =>
|
case AvatarResponse.Destroy(victim, killer, weapon, pos) =>
|
||||||
// guid = victim // killer = killer
|
// guid = victim // killer = killer
|
||||||
|
|
@ -398,71 +397,68 @@ class SessionAvatarHandlers(
|
||||||
case AvatarResponse.UpdateKillsDeathsAssists(_, kda) =>
|
case AvatarResponse.UpdateKillsDeathsAssists(_, kda) =>
|
||||||
avatarActor ! AvatarActor.UpdateKillsDeathsAssists(kda)
|
avatarActor ! AvatarActor.UpdateKillsDeathsAssists(kda)
|
||||||
|
|
||||||
case AvatarResponse.AwardBep(_, bep, expType) =>
|
case AvatarResponse.AwardBep(charId, bep, expType) =>
|
||||||
avatarActor ! AvatarActor.AwardBep(bep, expType)
|
if (charId == player.CharId) {
|
||||||
|
avatarActor ! AvatarActor.AwardBep(bep, expType)
|
||||||
|
}
|
||||||
|
|
||||||
case AvatarResponse.AwardCep(0, cep) =>
|
case AvatarResponse.AwardCep(charId, cep) =>
|
||||||
|
//if the target player, always award (some) CEP
|
||||||
|
if (charId == player.CharId) {
|
||||||
|
avatarActor ! AvatarActor.AwardCep(cep)
|
||||||
|
}
|
||||||
|
|
||||||
|
case AvatarResponse.FacilityCaptureRewards(buildingId, zoneNumber, cep) =>
|
||||||
//must be in a squad to earn experience
|
//must be in a squad to earn experience
|
||||||
val id = player.CharId
|
val cepConfig = Config.app.game.experience.cep
|
||||||
|
val charId = player.CharId
|
||||||
val squadUI = sessionData.squad.squadUI
|
val squadUI = sessionData.squad.squadUI
|
||||||
val participation = continent
|
val participation = continent
|
||||||
.Buildings
|
.Building(buildingId)
|
||||||
.values
|
.map { building =>
|
||||||
.collect { case building if {
|
|
||||||
val soi = building.Definition.SOIRadius * building.Definition.SOIRadius
|
|
||||||
val pos = player.Position.xy
|
|
||||||
Vector3.DistanceSquared(building.Position.xy, pos) < soi
|
|
||||||
} =>
|
|
||||||
building.Participation.PlayerContribution()
|
building.Participation.PlayerContribution()
|
||||||
}
|
}
|
||||||
squadUI
|
squadUI
|
||||||
.find { _._1 == id }
|
.find { _._1 == charId }
|
||||||
.collect {
|
.collect {
|
||||||
case (_, elem) if elem.index == 0 =>
|
case (_, elem) if elem.index == 0 =>
|
||||||
//squad leader earns CEP, modified by squad effort
|
//squad leader earns CEP, modified by squad effort, capped by squad size present during the capture
|
||||||
val maxRate: Long = {
|
val squadParticipation = participation match {
|
||||||
val maxCepList = Config.app.game.maximumCepPerSquadSize
|
case Some(map) => map.filter { case (id, _) => squadUI.contains(id) }
|
||||||
val squadSize: Int = {
|
case _ => Map.empty[Long, Float]
|
||||||
val squadSizeList: Iterable[Int] = participation
|
}
|
||||||
.map { facilityMap =>
|
val maxCepBySquadSize: Long = {
|
||||||
squadUI.count { case (id, _) => facilityMap.contains(id) }
|
val maxCepList = cepConfig.maximumPerSquadSize
|
||||||
}
|
val squadSize: Int = squadParticipation.size
|
||||||
if (squadSizeList.nonEmpty) {
|
|
||||||
squadSizeList.max
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
maxCepList.lift(squadSize - 1).getOrElse(squadSize * maxCepList.head).toLong
|
maxCepList.lift(squadSize - 1).getOrElse(squadSize * maxCepList.head).toLong
|
||||||
}
|
}
|
||||||
val groupContribution: Float = {
|
val groupContribution: Float = squadUI
|
||||||
val eachSquadMemberParticipation: Iterable[Float] = participation.map { facilityMap =>
|
.map { case (id, _) => (id, squadParticipation.getOrElse(id, 0f) / 10f) }
|
||||||
val foundSquadMemberParticipation: Iterable[Float] = squadUI
|
.values
|
||||||
.keys
|
.max
|
||||||
.flatMap { facilityMap.get }
|
val modifiedExp: Long = (cep.toFloat * groupContribution).toLong
|
||||||
if (foundSquadMemberParticipation.nonEmpty) {
|
val cappedModifiedExp: Long = math.min(modifiedExp, maxCepBySquadSize)
|
||||||
foundSquadMemberParticipation.sum / 10f
|
val finalExp: Long = if (modifiedExp > cappedModifiedExp) {
|
||||||
} else {
|
val overLimitOverflow = if (cepConfig.squadSizeLimitOverflow == -1) {
|
||||||
0f
|
cep.toFloat
|
||||||
}
|
|
||||||
}
|
|
||||||
if (eachSquadMemberParticipation.nonEmpty) {
|
|
||||||
eachSquadMemberParticipation.max
|
|
||||||
} else {
|
} else {
|
||||||
0
|
cepConfig.squadSizeLimitOverflow.toFloat
|
||||||
}
|
}
|
||||||
|
cappedModifiedExp + (overLimitOverflow * cepConfig.squadSizeLimitOverflowMultiplier).toLong
|
||||||
|
} else {
|
||||||
|
cappedModifiedExp
|
||||||
}
|
}
|
||||||
val modifiedExp: Long = math.min((cep.toFloat * groupContribution).toLong, maxRate)
|
exp.ToDatabase.reportFacilityCapture(charId, buildingId, zoneNumber, finalExp, expType="cep")
|
||||||
avatarActor ! AvatarActor.AwardCep(modifiedExp)
|
avatarActor ! AvatarActor.AwardCep(finalExp)
|
||||||
Some(modifiedExp)
|
Some(finalExp)
|
||||||
|
|
||||||
case _ =>
|
case _ =>
|
||||||
//squad member earns BEP based on CEP, modified by personal effort
|
//squad member earns BEP based on CEP, modified by personal effort
|
||||||
val individualContribution = {
|
val individualContribution = {
|
||||||
val contributionList = for {
|
val contributionList = for {
|
||||||
facilityMap <- participation
|
facilityMap <- participation
|
||||||
if facilityMap.contains(id)
|
if facilityMap.contains(charId)
|
||||||
} yield facilityMap(id)
|
} yield facilityMap(charId)
|
||||||
if (contributionList.nonEmpty) {
|
if (contributionList.nonEmpty) {
|
||||||
contributionList.max
|
contributionList.max
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -470,26 +466,11 @@ class SessionAvatarHandlers(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val modifiedExp = (cep * individualContribution).toLong
|
val modifiedExp = (cep * individualContribution).toLong
|
||||||
|
exp.ToDatabase.reportFacilityCapture(charId, buildingId, zoneNumber, modifiedExp, expType="bep")
|
||||||
avatarActor ! AvatarActor.AwardBep(modifiedExp, ExperienceType.Normal)
|
avatarActor ! AvatarActor.AwardBep(modifiedExp, ExperienceType.Normal)
|
||||||
Some(modifiedExp)
|
Some(modifiedExp)
|
||||||
}
|
}
|
||||||
|
|
||||||
case AvatarResponse.AwardCep(charId, cep) =>
|
|
||||||
//if the target player, always award (some) CEP
|
|
||||||
val squadUI = sessionData.squad.squadUI
|
|
||||||
if (charId == player.CharId) {
|
|
||||||
val maxRate: Long = squadUI.find { _._1 == avatar.id } match {
|
|
||||||
case Some((_, elem)) if elem.index == 0 =>
|
|
||||||
val thisZone = continent.Number
|
|
||||||
val squadSize = squadUI.count { case (_, e) => e.zone == thisZone } - 1
|
|
||||||
val maxCepList = Config.app.game.maximumCepPerSquadSize
|
|
||||||
maxCepList.lift(squadSize).getOrElse(squadSize * maxCepList.head).toLong
|
|
||||||
case _ =>
|
|
||||||
Config.app.game.maximumCepPerSquadSize.head.toLong
|
|
||||||
}
|
|
||||||
avatarActor ! AvatarActor.AwardCep(math.min(cep, maxRate))
|
|
||||||
}
|
|
||||||
|
|
||||||
case AvatarResponse.SendResponse(msg) =>
|
case AvatarResponse.SendResponse(msg) =>
|
||||||
sendResponse(msg)
|
sendResponse(msg)
|
||||||
|
|
||||||
|
|
@ -526,6 +507,7 @@ class SessionAvatarHandlers(
|
||||||
sessionData.renewCharSavedTimer(fixedLen = 1800L, varLen = 0L)
|
sessionData.renewCharSavedTimer(fixedLen = 1800L, varLen = 0L)
|
||||||
|
|
||||||
//player state changes
|
//player state changes
|
||||||
|
AvatarActor.updateToolDischargeFor(avatar)
|
||||||
player.FreeHand.Equipment.foreach { item =>
|
player.FreeHand.Equipment.foreach { item =>
|
||||||
DropEquipmentFromInventory(player)(item)
|
DropEquipmentFromInventory(player)(item)
|
||||||
}
|
}
|
||||||
|
|
@ -540,9 +522,7 @@ class SessionAvatarHandlers(
|
||||||
}
|
}
|
||||||
sessionData.playerActionsToCancel()
|
sessionData.playerActionsToCancel()
|
||||||
sessionData.terminals.CancelAllProximityUnits()
|
sessionData.terminals.CancelAllProximityUnits()
|
||||||
sessionData.zoning
|
|
||||||
AvatarActor.savePlayerLocation(player)
|
AvatarActor.savePlayerLocation(player)
|
||||||
sessionData.shooting.reportOngoingShots(sessionData.shooting.reportOngoingShotsToDatabase)
|
|
||||||
sessionData.zoning.spawn.shiftPosition = Some(player.Position)
|
sessionData.zoning.spawn.shiftPosition = Some(player.Position)
|
||||||
|
|
||||||
//respawn
|
//respawn
|
||||||
|
|
|
||||||
|
|
@ -2458,9 +2458,9 @@ class SessionData(
|
||||||
src: PlanetSideGameObject with TelepadLike,
|
src: PlanetSideGameObject with TelepadLike,
|
||||||
dest: PlanetSideGameObject with TelepadLike
|
dest: PlanetSideGameObject with TelepadLike
|
||||||
): Unit = {
|
): Unit = {
|
||||||
val time = System.nanoTime
|
val time = System.currentTimeMillis()
|
||||||
if (
|
if (
|
||||||
time - recentTeleportAttempt > (2 seconds).toNanos && router.DeploymentState == DriveState.Deployed &&
|
time - recentTeleportAttempt > 2000L && router.DeploymentState == DriveState.Deployed &&
|
||||||
internalTelepad.Active &&
|
internalTelepad.Active &&
|
||||||
remoteTelepad.Active
|
remoteTelepad.Active
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import akka.actor.{ActorContext, ActorRef, Cancellable, typed}
|
||||||
import akka.pattern.ask
|
import akka.pattern.ask
|
||||||
import akka.util.Timeout
|
import akka.util.Timeout
|
||||||
import net.psforever.login.WorldSession
|
import net.psforever.login.WorldSession
|
||||||
|
import net.psforever.objects.avatar.scoring.ScoreCard
|
||||||
import net.psforever.objects.inventory.InventoryItem
|
import net.psforever.objects.inventory.InventoryItem
|
||||||
import net.psforever.objects.serverobject.mount.Seat
|
import net.psforever.objects.serverobject.mount.Seat
|
||||||
import net.psforever.objects.serverobject.tube.SpawnTube
|
import net.psforever.objects.serverobject.tube.SpawnTube
|
||||||
|
|
@ -2342,6 +2343,7 @@ class ZoningOperations(
|
||||||
player.avatar = player.avatar.copy(stamina = avatar.maxStamina)
|
player.avatar = player.avatar.copy(stamina = avatar.maxStamina)
|
||||||
avatarActor ! AvatarActor.RestoreStamina(avatar.maxStamina)
|
avatarActor ! AvatarActor.RestoreStamina(avatar.maxStamina)
|
||||||
avatarActor ! AvatarActor.ResetImplants()
|
avatarActor ! AvatarActor.ResetImplants()
|
||||||
|
zones.exp.ToDatabase.reportRespawns(tplayer.CharId, ScoreCard.reviveCount(player.avatar.scorecard.CurrentLife))
|
||||||
val obj = Player.Respawn(tplayer)
|
val obj = Player.Respawn(tplayer)
|
||||||
DefinitionUtil.applyDefaultLoadout(obj)
|
DefinitionUtil.applyDefaultLoadout(obj)
|
||||||
obj.death_by = tplayer.death_by
|
obj.death_by = tplayer.death_by
|
||||||
|
|
|
||||||
|
|
@ -9847,6 +9847,7 @@ object GlobalDefinitions {
|
||||||
resource_silo.Damageable = false
|
resource_silo.Damageable = false
|
||||||
resource_silo.Repairable = false
|
resource_silo.Repairable = false
|
||||||
resource_silo.MaxNtuCapacitor = 1000
|
resource_silo.MaxNtuCapacitor = 1000
|
||||||
|
resource_silo.ChargeTime = 105.seconds //from 0-100% in roughly 105s on live (~20%-100% https://youtu.be/veOWToR2nSk?t=1402)
|
||||||
|
|
||||||
capture_terminal.Name = "capture_terminal"
|
capture_terminal.Name = "capture_terminal"
|
||||||
capture_terminal.Damageable = false
|
capture_terminal.Damageable = false
|
||||||
|
|
@ -9856,7 +9857,7 @@ object GlobalDefinitions {
|
||||||
secondary_capture.Name = "secondary_capture"
|
secondary_capture.Name = "secondary_capture"
|
||||||
secondary_capture.Damageable = false
|
secondary_capture.Damageable = false
|
||||||
secondary_capture.Repairable = false
|
secondary_capture.Repairable = false
|
||||||
secondary_capture.FacilityHackTime = 1.nanosecond
|
secondary_capture.FacilityHackTime = 1.millisecond
|
||||||
|
|
||||||
vanu_control_console.Name = "vanu_control_console"
|
vanu_control_console.Name = "vanu_control_console"
|
||||||
vanu_control_console.Damageable = false
|
vanu_control_console.Damageable = false
|
||||||
|
|
|
||||||
|
|
@ -110,6 +110,7 @@ class Player(var avatar: Avatar)
|
||||||
Health = Definition.DefaultHealth
|
Health = Definition.DefaultHealth
|
||||||
Armor = MaxArmor
|
Armor = MaxArmor
|
||||||
Capacitor = 0
|
Capacitor = 0
|
||||||
|
avatar.scorecard.respawn()
|
||||||
released = false
|
released = false
|
||||||
}
|
}
|
||||||
isAlive
|
isAlive
|
||||||
|
|
@ -124,13 +125,16 @@ class Player(var avatar: Avatar)
|
||||||
def Revive: Boolean = {
|
def Revive: Boolean = {
|
||||||
Destroyed = false
|
Destroyed = false
|
||||||
Health = Definition.DefaultHealth
|
Health = Definition.DefaultHealth
|
||||||
|
avatar.scorecard.revive()
|
||||||
released = false
|
released = false
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
def Release: Boolean = {
|
def Release: Boolean = {
|
||||||
released = true
|
if (!released) {
|
||||||
backpack = !isAlive
|
released = true
|
||||||
|
backpack = !isAlive
|
||||||
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -626,6 +630,7 @@ object Player {
|
||||||
if (player.Release) {
|
if (player.Release) {
|
||||||
val obj = new Player(player.avatar)
|
val obj = new Player(player.avatar)
|
||||||
obj.Continent = player.Continent
|
obj.Continent = player.Continent
|
||||||
|
obj.avatar.scorecard.respawn()
|
||||||
obj
|
obj
|
||||||
} else {
|
} else {
|
||||||
player
|
player
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ class Tool(private val toolDef: ToolDefinition)
|
||||||
}
|
}
|
||||||
|
|
||||||
def Discharge(rounds: Option[Int] = None): Int = {
|
def Discharge(rounds: Option[Int] = None): Int = {
|
||||||
lastDischarge = System.nanoTime()
|
lastDischarge = System.currentTimeMillis()
|
||||||
Magazine = FireMode.Discharge(this, rounds)
|
Magazine = FireMode.Discharge(this, rounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,14 @@ final case class Life(
|
||||||
assists: Seq[Assist],
|
assists: Seq[Assist],
|
||||||
death: Option[Death],
|
death: Option[Death],
|
||||||
equipmentStats: Seq[EquipmentStat],
|
equipmentStats: Seq[EquipmentStat],
|
||||||
supportExperience: Long
|
supportExperience: Long,
|
||||||
|
prior: Option[Life]
|
||||||
)
|
)
|
||||||
|
|
||||||
object Life {
|
object Life {
|
||||||
def apply(): Life = Life(Nil, Nil, None, Nil, 0)
|
def apply(): Life = Life(Nil, Nil, None, Nil, 0, None)
|
||||||
|
|
||||||
|
def revive(prior: Life): Life = Life(Nil, Nil, None, Nil, 0, Some(prior))
|
||||||
|
|
||||||
def bep(life: Life): Long = {
|
def bep(life: Life): Long = {
|
||||||
life.kills.foldLeft(0L)(_ + _.experienceEarned) + life.assists.foldLeft(0L)(_ + _.experienceEarned)
|
life.kills.foldLeft(0L)(_ + _.experienceEarned) + life.assists.foldLeft(0L)(_ + _.experienceEarned)
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ class ScoreCard() {
|
||||||
|
|
||||||
def Lives: Seq[Life] = lives
|
def Lives: Seq[Life] = lives
|
||||||
|
|
||||||
|
def AllLives: Seq[Life] = curr +: lives
|
||||||
|
|
||||||
def Kills: Seq[Kill] = lives.flatMap { _.kills } ++ curr.kills
|
def Kills: Seq[Kill] = lives.flatMap { _.kills } ++ curr.kills
|
||||||
|
|
||||||
def KillStatistics: Map[Int, Statistic] = killStatistics.toMap
|
def KillStatistics: Map[Int, Statistic] = killStatistics.toMap
|
||||||
|
|
@ -39,17 +41,41 @@ class ScoreCard() {
|
||||||
ScoreCard.updateStatisticsFor(assistStatistics, wid.equipment, faction)
|
ScoreCard.updateStatisticsFor(assistStatistics, wid.equipment, faction)
|
||||||
}
|
}
|
||||||
case d: Death =>
|
case d: Death =>
|
||||||
val expired = curr
|
curr = curr.copy(death = Some(d))
|
||||||
curr = Life()
|
|
||||||
lives = expired.copy(death = Some(d)) +: lives
|
|
||||||
case value: Long =>
|
case value: Long =>
|
||||||
curr = curr.copy(supportExperience = curr.supportExperience + value)
|
curr = curr.copy(supportExperience = curr.supportExperience + value)
|
||||||
case _ => ()
|
case _ => ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def revive(): Unit = {
|
||||||
|
curr = Life.revive(curr)
|
||||||
|
}
|
||||||
|
|
||||||
|
def respawn(): Unit = {
|
||||||
|
val death = curr
|
||||||
|
curr = Life()
|
||||||
|
lives = death +: lives
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object ScoreCard {
|
object ScoreCard {
|
||||||
|
def respawnCount(card: ScoreCard): Int = {
|
||||||
|
card.AllLives.foldLeft(0)(_ + recursiveReviveCount(_, count = 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
def reviveCount(life: Life): Int = {
|
||||||
|
recursiveReviveCount(life, count = 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@tailrec
|
||||||
|
private def recursiveReviveCount(life: Life, count: Int): Int = {
|
||||||
|
life.prior match {
|
||||||
|
case None => count
|
||||||
|
case Some(previousLife) => recursiveReviveCount(previousLife, count + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private def updateEquipmentStat(curr: Life, entry: EquipmentStat): Life = {
|
private def updateEquipmentStat(curr: Life, entry: EquipmentStat): Life = {
|
||||||
updateEquipmentStat(curr, entry, entry.objectId, entry.kills, entry.assists)
|
updateEquipmentStat(curr, entry, entry.objectId, entry.kills, entry.assists)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ class CaptureFlagConverter extends ObjectCreateConverter[CaptureFlag]() {
|
||||||
case _ => Hackable.HackInfo(PlayerSource("", PlanetSideEmpire.NEUTRAL, Vector3.Zero), PlanetSideGUID(0), 0L, 0L)
|
case _ => Hackable.HackInfo(PlayerSource("", PlanetSideEmpire.NEUTRAL, Vector3.Zero), PlanetSideGUID(0), 0L, 0L)
|
||||||
}
|
}
|
||||||
|
|
||||||
val millisecondsRemaining = TimeUnit.MILLISECONDS.convert(math.max(0, hackInfo.hackStartTime + hackInfo.hackDuration - System.nanoTime), TimeUnit.NANOSECONDS)
|
val millisecondsRemaining = math.max(0, hackInfo.hackStartTime + hackInfo.hackDuration - System.currentTimeMillis())
|
||||||
|
|
||||||
Success(
|
Success(
|
||||||
CaptureFlagData(
|
CaptureFlagData(
|
||||||
|
|
|
||||||
|
|
@ -25,14 +25,14 @@ trait Hackable {
|
||||||
def HackedBy_=(agent: Option[Player]): Option[HackInfo] = {
|
def HackedBy_=(agent: Option[Player]): Option[HackInfo] = {
|
||||||
(hackedBy, agent) match {
|
(hackedBy, agent) match {
|
||||||
case (None, Some(actor)) =>
|
case (None, Some(actor)) =>
|
||||||
hackedBy = Some(HackInfo(PlayerSource(actor), actor.GUID, System.nanoTime, 0L))
|
hackedBy = Some(HackInfo(PlayerSource(actor), actor.GUID, System.currentTimeMillis(), 0L))
|
||||||
case (Some(info), Some(actor)) =>
|
case (Some(info), Some(actor)) =>
|
||||||
if (actor.Faction == this.Faction) {
|
if (actor.Faction == this.Faction) {
|
||||||
//hack cleared
|
//hack cleared
|
||||||
hackedBy = None
|
hackedBy = None
|
||||||
} else if (actor.Faction != info.hackerFaction) {
|
} else if (actor.Faction != info.hackerFaction) {
|
||||||
//override the hack state with a new hack state if the new user has different faction affiliation
|
//override the hack state with a new hack state if the new user has different faction affiliation
|
||||||
hackedBy = Some(HackInfo(PlayerSource(actor), actor.GUID, System.nanoTime, 0L))
|
hackedBy = Some(HackInfo(PlayerSource(actor), actor.GUID, System.currentTimeMillis(), 0L))
|
||||||
}
|
}
|
||||||
case (_, None) =>
|
case (_, None) =>
|
||||||
hackedBy = None
|
hackedBy = None
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,13 @@ import net.psforever.actors.zone.BuildingActor
|
||||||
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
||||||
import net.psforever.objects.serverobject.transfer.TransferBehavior
|
import net.psforever.objects.serverobject.transfer.TransferBehavior
|
||||||
import net.psforever.objects.serverobject.structures.Building
|
import net.psforever.objects.serverobject.structures.Building
|
||||||
|
import net.psforever.objects.zones
|
||||||
import net.psforever.objects.{GlobalDefinitions, Ntu, NtuContainer, NtuStorageBehavior, Vehicle}
|
import net.psforever.objects.{GlobalDefinitions, Ntu, NtuContainer, NtuStorageBehavior, Vehicle}
|
||||||
import net.psforever.types.{ExperienceType, PlanetSideEmpire}
|
import net.psforever.types.{ExperienceType, PlanetSideEmpire}
|
||||||
import net.psforever.services.Service
|
import net.psforever.services.Service
|
||||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||||
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||||
|
import net.psforever.util.Config
|
||||||
|
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
@ -188,11 +190,16 @@ class ResourceSiloControl(resourceSilo: ResourceSilo)
|
||||||
.map { v => (v, v.Owners) }
|
.map { v => (v, v.Owners) }
|
||||||
.collect { case (vehicle, Some(owner)) =>
|
.collect { case (vehicle, Some(owner)) =>
|
||||||
//experience is reported as normal
|
//experience is reported as normal
|
||||||
val deposit: Long = 100L * math.floor(amount).toLong / math.floor(resourceSilo.MaxNtuCapacitor / 105f).toLong
|
val deposit: Long =
|
||||||
|
(Config.app.game.experience.sep.ntuSiloDepositReward.toFloat *
|
||||||
|
math.floor(amount).toFloat /
|
||||||
|
math.floor(resourceSilo.MaxNtuCapacitor / resourceSilo.Definition.ChargeTime.toMillis.toFloat)
|
||||||
|
).toLong
|
||||||
vehicle.Zone.AvatarEvents ! AvatarServiceMessage(
|
vehicle.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||||
owner.name,
|
owner.name,
|
||||||
AvatarAction.AwardBep(0, deposit, ExperienceType.Normal)
|
AvatarAction.AwardBep(0, deposit, ExperienceType.Normal)
|
||||||
)
|
)
|
||||||
|
zones.exp.ToDatabase.reportNtuActivity(owner.charId, resourceSilo.Zone.Number, resourceSilo.Owner.GUID.guid, deposit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,16 @@ package net.psforever.objects.serverobject.resourcesilo
|
||||||
import net.psforever.objects.NtuContainerDefinition
|
import net.psforever.objects.NtuContainerDefinition
|
||||||
import net.psforever.objects.serverobject.structures.AmenityDefinition
|
import net.psforever.objects.serverobject.structures.AmenityDefinition
|
||||||
|
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The definition for any `Resource Silo`.
|
* The definition for any `Resource Silo`.
|
||||||
* Object Id 731.
|
* Object Id 731.
|
||||||
*/
|
*/
|
||||||
class ResourceSiloDefinition extends AmenityDefinition(731)
|
class ResourceSiloDefinition extends AmenityDefinition(731)
|
||||||
with NtuContainerDefinition {
|
with NtuContainerDefinition {
|
||||||
|
var ChargeTime: FiniteDuration = 0.seconds
|
||||||
|
|
||||||
Name = "resource_silo"
|
Name = "resource_silo"
|
||||||
MaxNtuCapacitor = 1000
|
MaxNtuCapacitor = 1000
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -183,8 +183,7 @@ class Building(
|
||||||
case Some(obj: CaptureTerminal with Hackable) =>
|
case Some(obj: CaptureTerminal with Hackable) =>
|
||||||
obj.HackedBy match {
|
obj.HackedBy match {
|
||||||
case Some(Hackable.HackInfo(p, _, start, length)) =>
|
case Some(Hackable.HackInfo(p, _, start, length)) =>
|
||||||
val hack_time_remaining_ms =
|
val hack_time_remaining_ms = math.max(0, start + length - System.currentTimeMillis())
|
||||||
TimeUnit.MILLISECONDS.convert(math.max(0, start + length - System.nanoTime), TimeUnit.NANOSECONDS)
|
|
||||||
(true, p.Faction, hack_time_remaining_ms)
|
(true, p.Faction, hack_time_remaining_ms)
|
||||||
case _ =>
|
case _ =>
|
||||||
(false, PlanetSideEmpire.NEUTRAL, 0L)
|
(false, PlanetSideEmpire.NEUTRAL, 0L)
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,12 @@ import scala.collection.mutable
|
||||||
trait FacilityHackParticipation extends ParticipationLogic {
|
trait FacilityHackParticipation extends ParticipationLogic {
|
||||||
protected var lastInfoRequest: Long = 0L
|
protected var lastInfoRequest: Long = 0L
|
||||||
protected var infoRequestOverTime: Seq[Long] = Seq[Long]()
|
protected var infoRequestOverTime: Seq[Long] = Seq[Long]()
|
||||||
/*
|
/**
|
||||||
key: unique player identifier
|
key: unique player identifier<br>
|
||||||
|
values: last player entry, number of times updated, time of last update (POSIX time)
|
||||||
*/
|
*/
|
||||||
protected var playerContribution: mutable.LongMap[(Player, Int, Long)] = mutable.LongMap[(Player, Int, Long)]()
|
protected var playerContribution: mutable.LongMap[(Player, Int, Long)] = mutable.LongMap[(Player, Int, Long)]()
|
||||||
protected var playerPopulationOverTime: Seq[Map[PlanetSideEmpire.Value, Int]] = Seq[ Map[PlanetSideEmpire.Value, Int]]()
|
protected var playerPopulationOverTime: Seq[Map[PlanetSideEmpire.Value, Int]] = Seq[Map[PlanetSideEmpire.Value, Int]]()
|
||||||
|
|
||||||
def PlayerContribution(timeDelay: Long): Map[Long, Float] = {
|
def PlayerContribution(timeDelay: Long): Map[Long, Float] = {
|
||||||
playerContribution
|
playerContribution
|
||||||
|
|
@ -49,6 +50,13 @@ trait FacilityHackParticipation extends ParticipationLogic {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eliminate participation for players who have no submitted updates within the time period.
|
||||||
|
* @param list current list of players
|
||||||
|
* @param now current time (ms)
|
||||||
|
* @param before how long before the current time beyond which players should be eliminated (ms)
|
||||||
|
* @see `timeSensitiveFilterAndAppend`
|
||||||
|
*/
|
||||||
protected def updatePopulationOverTime(list: List[Player], now: Long, before: Long): Unit = {
|
protected def updatePopulationOverTime(list: List[Player], now: Long, before: Long): Unit = {
|
||||||
var populationList = list
|
var populationList = list
|
||||||
val layer = PlanetSideEmpire.values.map { faction =>
|
val layer = PlanetSideEmpire.values.map { faction =>
|
||||||
|
|
@ -56,70 +64,82 @@ trait FacilityHackParticipation extends ParticipationLogic {
|
||||||
populationList = everyoneElse
|
populationList = everyoneElse
|
||||||
(faction, isFaction.size)
|
(faction, isFaction.size)
|
||||||
}.toMap[PlanetSideEmpire.Value, Int]
|
}.toMap[PlanetSideEmpire.Value, Int]
|
||||||
playerPopulationOverTime = timeSensitiveFilterAndAppend(playerPopulationOverTime, layer, now, before)
|
playerPopulationOverTime = timeSensitiveFilterAndAppend(playerPopulationOverTime, layer, now - before)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected def updateTime(now: Long): Unit = {
|
||||||
|
infoRequestOverTime = timeSensitiveFilterAndAppend(infoRequestOverTime, now, now - 900000L)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eliminate entries from the primary input list based on time entries in a secondary time record list.
|
||||||
|
* The time record list must be updated independently.
|
||||||
|
* @param list input list whose entries are edited against time and then is appended
|
||||||
|
* @param newEntry new entry of the appropriate type to append to the end of the output list
|
||||||
|
* @param beforeTime how long before the current time beyond which entries in the input list should be eliminated (ms)
|
||||||
|
* @tparam T it does not matter what the type is
|
||||||
|
* @return the modified list
|
||||||
|
*/
|
||||||
protected def timeSensitiveFilterAndAppend[T](
|
protected def timeSensitiveFilterAndAppend[T](
|
||||||
list: Seq[T],
|
list: Seq[T],
|
||||||
newEntry: T,
|
newEntry: T,
|
||||||
now: Long = System.currentTimeMillis(),
|
beforeTime: Long
|
||||||
before: Long
|
|
||||||
): Seq[T] = {
|
): Seq[T] = {
|
||||||
infoRequestOverTime match {
|
infoRequestOverTime match {
|
||||||
case Nil =>
|
case Nil => Seq(newEntry)
|
||||||
Seq(newEntry)
|
|
||||||
case _ =>
|
case _ =>
|
||||||
val beforeTime = now - before
|
|
||||||
(infoRequestOverTime.indexWhere { _ >= beforeTime } match {
|
(infoRequestOverTime.indexWhere { _ >= beforeTime } match {
|
||||||
case -1 =>
|
case -1 => list
|
||||||
list
|
case cutOffIndex => list.drop(cutOffIndex)
|
||||||
case cutOffIndex =>
|
|
||||||
list.drop(cutOffIndex)
|
|
||||||
}) :+ newEntry
|
}) :+ newEntry
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object FacilityHackParticipation {
|
object FacilityHackParticipation {
|
||||||
private[participation] def calculateExperienceFromKills(
|
private[participation] def allocateKillsByPlayers(
|
||||||
center: Vector3,
|
center: Vector3,
|
||||||
radius: Float,
|
radius: Float,
|
||||||
hackStart: Long,
|
hackStart: Long,
|
||||||
completionTime: Long,
|
completionTime: Long,
|
||||||
opposingFaction: PlanetSideEmpire.Value,
|
opposingFaction: PlanetSideEmpire.Value,
|
||||||
contributionVictor: Iterable[(Player, Int, Long)],
|
contributionVictor: Iterable[(Player, Int, Long)],
|
||||||
contributionOpposingSize: Int
|
): Iterable[(UniquePlayer, Float, Seq[Kill])] = {
|
||||||
): Long = {
|
|
||||||
val killMapFunc: Iterable[(Player, Int, Long)] => Iterable[(UniquePlayer, Float, Seq[Kill])] = {
|
val killMapFunc: Iterable[(Player, Int, Long)] => Iterable[(UniquePlayer, Float, Seq[Kill])] = {
|
||||||
killsEarnedPerPlayerDuringHack(center.xy, radius * radius, hackStart, hackStart + completionTime, opposingFaction)
|
killsEarnedPerPlayerDuringHack(center.xy, radius * radius, hackStart, hackStart + completionTime, opposingFaction)
|
||||||
}
|
}
|
||||||
val killMapValues = killMapFunc(contributionVictor)
|
killMapFunc(contributionVictor)
|
||||||
val totalExperienceFromKills = killMapValues.flatMap { _._3.map { _.experienceEarned } }.sum
|
|
||||||
val experienceModifier = {
|
|
||||||
if (contributionOpposingSize > 0 && contributionOpposingSize < 10) {
|
|
||||||
contributionOpposingSize * 0.1f + math.random()
|
|
||||||
} else {
|
|
||||||
contributionOpposingSize * 0.1f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(totalExperienceFromKills * experienceModifier).toLong
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private def killsEarnedPerPlayerDuringHack(
|
private[participation] def calculateExperienceFromKills(
|
||||||
centerXY: Vector3,
|
killMapValues: Iterable[(UniquePlayer, Float, Seq[Kill])],
|
||||||
distanceSq: Float,
|
contributionOpposingSize: Int
|
||||||
start: Long,
|
): Long = {
|
||||||
end: Long,
|
val totalExperienceFromKills = killMapValues
|
||||||
faction: PlanetSideEmpire.Value
|
.flatMap { _._3.map { _.experienceEarned } }
|
||||||
)
|
.sum
|
||||||
(
|
.toFloat
|
||||||
list: Iterable[(Player, Int, Long)]
|
(totalExperienceFromKills * contributionOpposingSize.toFloat * 0.1d).toLong
|
||||||
): Iterable[(UniquePlayer, Float, Seq[Kill])] = {
|
}
|
||||||
|
|
||||||
|
private[participation] def killsEarnedPerPlayerDuringHack(
|
||||||
|
centerXY: Vector3,
|
||||||
|
distanceSq: Float,
|
||||||
|
start: Long,
|
||||||
|
end: Long,
|
||||||
|
faction: PlanetSideEmpire.Value
|
||||||
|
)
|
||||||
|
(
|
||||||
|
list: Iterable[(Player, Int, Long)]
|
||||||
|
): Iterable[(UniquePlayer, Float, Seq[Kill])] = {
|
||||||
val duration = end - start
|
val duration = end - start
|
||||||
list.map { case (p, d, _) =>
|
list.map { case (p, d, _) =>
|
||||||
val killList = p.avatar.scorecard.Kills.filter { k =>
|
val killList = p.avatar.scorecard.Kills.filter { k =>
|
||||||
val killTime = k.info.interaction.hitTime
|
val killTime = k.info.interaction.hitTime
|
||||||
k.victim.Faction == faction && start < killTime && killTime < end && Vector3.DistanceSquared(centerXY, k.info.interaction.hitPos.xy) < distanceSq
|
k.victim.Faction == faction &&
|
||||||
|
start < killTime &&
|
||||||
|
killTime <= end &&
|
||||||
|
Vector3.DistanceSquared(centerXY, k.info.interaction.hitPos.xy) < distanceSq
|
||||||
}
|
}
|
||||||
(PlayerSource(p).unique, math.min(d, duration).toFloat / duration.toFloat, killList)
|
(PlayerSource(p).unique, math.min(d, duration).toFloat / duration.toFloat, killList)
|
||||||
}
|
}
|
||||||
|
|
@ -220,7 +240,8 @@ object FacilityHackParticipation {
|
||||||
victorPop <- victorPopulationNumbers
|
victorPop <- victorPopulationNumbers
|
||||||
opposePop <- opposingPopulationNumbers
|
opposePop <- opposingPopulationNumbers
|
||||||
out = if (
|
out = if (
|
||||||
(opposePop < victorPop && opposePop * healthyPercentage > victorPop) ||
|
(opposePop + victorPop < 8) ||
|
||||||
|
(opposePop < victorPop && opposePop * healthyPercentage > victorPop) ||
|
||||||
(opposePop > victorPop && victorPop * healthyPercentage > opposePop)
|
(opposePop > victorPop && victorPop * healthyPercentage > opposePop)
|
||||||
) {
|
) {
|
||||||
1f //balanced enough population
|
1f //balanced enough population
|
||||||
|
|
@ -241,7 +262,7 @@ object FacilityHackParticipation {
|
||||||
overwhelmingOddsBonus: Long
|
overwhelmingOddsBonus: Long
|
||||||
): Long = {
|
): Long = {
|
||||||
if (opposingSize * steamrollPercentage < victorSize.toFloat) {
|
if (opposingSize * steamrollPercentage < victorSize.toFloat) {
|
||||||
-steamrollBonus * (victorSize - opposingSize) //steamroll by the victor
|
0L //steamroll by the victor
|
||||||
} else if (victorSize * overwhelmingOddsPercentage <= opposingSize.toFloat) {
|
} else if (victorSize * overwhelmingOddsPercentage <= opposingSize.toFloat) {
|
||||||
overwhelmingOddsBonus + opposingSize + victorSize //victory against overwhelming odds
|
overwhelmingOddsBonus + opposingSize + victorSize //victory against overwhelming odds
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,17 @@
|
||||||
// Copyright (c) 2023 PSForever
|
// Copyright (c) 2023 PSForever
|
||||||
package net.psforever.objects.serverobject.structures.participation
|
package net.psforever.objects.serverobject.structures.participation
|
||||||
|
|
||||||
import net.psforever.objects.serverobject.structures.Building
|
import net.psforever.objects.serverobject.structures.{Building, StructureType}
|
||||||
import net.psforever.objects.sourcing.PlayerSource
|
import net.psforever.objects.sourcing.{PlayerSource, UniquePlayer}
|
||||||
import net.psforever.objects.zones.{HotSpotInfo, ZoneHotSpotProjector}
|
import net.psforever.objects.zones.{HotSpotInfo, ZoneHotSpotProjector}
|
||||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||||
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
import net.psforever.types.{PlanetSideEmpire, PlanetSideGeneratorState, Vector3}
|
||||||
import net.psforever.util.Config
|
import net.psforever.util.Config
|
||||||
|
|
||||||
import akka.pattern.ask
|
import akka.pattern.ask
|
||||||
import akka.util.Timeout
|
import akka.util.Timeout
|
||||||
|
import net.psforever.objects.avatar.scoring.Kill
|
||||||
|
import net.psforever.objects.zones.exp.ToDatabase
|
||||||
|
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
|
|
@ -49,17 +51,13 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
|
||||||
requestLayers.completeWith(request)
|
requestLayers.completeWith(request)
|
||||||
request.onComplete {
|
request.onComplete {
|
||||||
case Success(ZoneHotSpotProjector.ExposedHeat(_, _, activity)) =>
|
case Success(ZoneHotSpotProjector.ExposedHeat(_, _, activity)) =>
|
||||||
hotSpotLayersOverTime = timeSensitiveFilterAndAppend(hotSpotLayersOverTime, activity, System.currentTimeMillis(), before = 900000L)
|
hotSpotLayersOverTime = timeSensitiveFilterAndAppend(hotSpotLayersOverTime, activity, System.currentTimeMillis() - 900000L)
|
||||||
case _ =>
|
case _ =>
|
||||||
requestLayers.completeWith(Future(ZoneHotSpotProjector.ExposedHeat(Vector3.Zero, 0, Nil)))
|
requestLayers.completeWith(Future(ZoneHotSpotProjector.ExposedHeat(Vector3.Zero, 0, Nil)))
|
||||||
}
|
}
|
||||||
requestLayers.future
|
requestLayers.future
|
||||||
}
|
}
|
||||||
|
|
||||||
private def updateTime(now: Long): Unit = {
|
|
||||||
infoRequestOverTime = timeSensitiveFilterAndAppend(infoRequestOverTime, now, now, before = 900000L)
|
|
||||||
}
|
|
||||||
|
|
||||||
def RewardFacilityCapture(
|
def RewardFacilityCapture(
|
||||||
defenderFaction: PlanetSideEmpire.Value,
|
defenderFaction: PlanetSideEmpire.Value,
|
||||||
attackingFaction: PlanetSideEmpire.Value,
|
attackingFaction: PlanetSideEmpire.Value,
|
||||||
|
|
@ -70,11 +68,12 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
|
||||||
): Unit = {
|
): Unit = {
|
||||||
val curr = System.currentTimeMillis()
|
val curr = System.currentTimeMillis()
|
||||||
val hackStart = curr - completionTime
|
val hackStart = curr - completionTime
|
||||||
val (victorFaction, opposingFaction, flagCarrier) = if (!isResecured) {
|
val socketOpt = building.GetFlagSocket
|
||||||
val carrier = building.GetFlagSocket.flatMap(_.previousFlag).flatMap(_.Carrier)
|
val (victorFaction, opposingFaction, hasFlag, flagCarrier) = if (!isResecured) {
|
||||||
(attackingFaction, defenderFaction, carrier)
|
val carrier = socketOpt.flatMap(_.previousFlag).flatMap(_.Carrier)
|
||||||
|
(attackingFaction, defenderFaction, socketOpt.nonEmpty, carrier)
|
||||||
} else {
|
} else {
|
||||||
(defenderFaction, attackingFaction, None)
|
(defenderFaction, attackingFaction, socketOpt.nonEmpty, None)
|
||||||
}
|
}
|
||||||
val (contributionVictor, contributionOpposing, _) = {
|
val (contributionVictor, contributionOpposing, _) = {
|
||||||
val (a, b1) = playerContribution.partition { case (_, (p, _, _)) => p.Faction == victorFaction }
|
val (a, b1) = playerContribution.partition { case (_, (p, _, _)) => p.Faction == victorFaction }
|
||||||
|
|
@ -83,17 +82,6 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
|
||||||
}
|
}
|
||||||
val contributionVictorSize = contributionVictor.size
|
val contributionVictorSize = contributionVictor.size
|
||||||
if (contributionVictorSize > 0) {
|
if (contributionVictorSize > 0) {
|
||||||
val contributionOpposingSize = contributionOpposing.size
|
|
||||||
//1) experience from killing opposingFaction across duration of hack
|
|
||||||
val baseExperienceFromFacilityCapture: Long = FacilityHackParticipation.calculateExperienceFromKills(
|
|
||||||
building.Position,
|
|
||||||
building.Definition.SOIRadius - 50f,
|
|
||||||
hackStart,
|
|
||||||
completionTime,
|
|
||||||
opposingFaction,
|
|
||||||
contributionVictor,
|
|
||||||
contributionOpposingSize
|
|
||||||
)
|
|
||||||
//setup for ...
|
//setup for ...
|
||||||
val populationIndices = playerPopulationOverTime.indices
|
val populationIndices = playerPopulationOverTime.indices
|
||||||
val allFactions = PlanetSideEmpire.values.filterNot { _ == PlanetSideEmpire.NEUTRAL }.toSeq
|
val allFactions = PlanetSideEmpire.values.filterNot { _ == PlanetSideEmpire.NEUTRAL }.toSeq
|
||||||
|
|
@ -103,32 +91,43 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
|
||||||
}.toMap[PlanetSideEmpire.Value, Seq[Int]]
|
}.toMap[PlanetSideEmpire.Value, Seq[Int]]
|
||||||
(individualPopulationByLayer(victorFaction), individualPopulationByLayer(opposingFaction))
|
(individualPopulationByLayer(victorFaction), individualPopulationByLayer(opposingFaction))
|
||||||
}
|
}
|
||||||
|
val contributionOpposingSize = contributionOpposing.size
|
||||||
|
val killsByPlayersNotInTower = eliminateClosestTowerFromParticipating(
|
||||||
|
building,
|
||||||
|
FacilityHackParticipation.allocateKillsByPlayers(
|
||||||
|
building.Position,
|
||||||
|
building.Definition.SOIRadius.toFloat,
|
||||||
|
hackStart,
|
||||||
|
completionTime,
|
||||||
|
opposingFaction,
|
||||||
|
contributionOpposing
|
||||||
|
)
|
||||||
|
)
|
||||||
|
//1) experience from killing opposingFaction across duration of hack
|
||||||
|
//The kills that occurred in the facility's attached field tower's sphere of influence have been eliminated from consideration.
|
||||||
|
val baseExperienceFromFacilityCapture: Long = FacilityHackParticipation.calculateExperienceFromKills(
|
||||||
|
killsByPlayersNotInTower,
|
||||||
|
contributionOpposingSize
|
||||||
|
)
|
||||||
//2) peak population modifier
|
//2) peak population modifier
|
||||||
|
//Large facility battles should be well-rewarded.
|
||||||
val populationModifier = FacilityHackParticipation.populationProgressModifier(
|
val populationModifier = FacilityHackParticipation.populationProgressModifier(
|
||||||
opposingPopulationByLayer,
|
opposingPopulationByLayer,
|
||||||
{ pop =>
|
{ pop =>
|
||||||
if (pop > 59) 0.5f
|
if (pop > 75) 0.9f
|
||||||
else if (pop > 29) 0.4f
|
else if (pop > 59) 0.6f
|
||||||
else if (pop > 25) 0.3f
|
else if (pop > 29) 0.55f
|
||||||
else 0.25f
|
else if (pop > 25) 0.5f
|
||||||
|
else 0.45f
|
||||||
},
|
},
|
||||||
3
|
4
|
||||||
)
|
)
|
||||||
//3) competition bonus
|
//3) competition multiplier
|
||||||
val competitionBonus: Long = FacilityHackParticipation.competitionBonus(
|
|
||||||
contributionVictorSize,
|
|
||||||
contributionOpposingSize,
|
|
||||||
steamrollPercentage = 1.25f,
|
|
||||||
steamrollBonus = 5L,
|
|
||||||
overwhelmingOddsPercentage = 0.5f,
|
|
||||||
overwhelmingOddsBonus = 100L
|
|
||||||
)
|
|
||||||
//4) competition multiplier
|
|
||||||
val competitionMultiplier: Float = {
|
val competitionMultiplier: Float = {
|
||||||
val populationBalanceModifier: Float = FacilityHackParticipation.populationBalanceModifier(
|
val populationBalanceModifier: Float = FacilityHackParticipation.populationBalanceModifier(
|
||||||
victorPopulationByLayer,
|
victorPopulationByLayer,
|
||||||
opposingPopulationByLayer,
|
opposingPopulationByLayer,
|
||||||
healthyPercentage = 1.25f
|
healthyPercentage = 1.5f
|
||||||
)
|
)
|
||||||
//compensate for heat
|
//compensate for heat
|
||||||
val regionHeatMapProgression = {
|
val regionHeatMapProgression = {
|
||||||
|
|
@ -140,7 +139,11 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
|
||||||
((A-1, B-2, C-3), (D-1, E-2, F-3), (G-1, H-2, I-3)) ... (1->(A, D, G), 2->(B, E, H), 3->(C, F, I))
|
((A-1, B-2, C-3), (D-1, E-2, F-3), (G-1, H-2, I-3)) ... (1->(A, D, G), 2->(B, E, H), 3->(C, F, I))
|
||||||
*/
|
*/
|
||||||
val finalMap = mutable.HashMap[Vector3, Map[PlanetSideEmpire.Value, Seq[Long]]]()
|
val finalMap = mutable.HashMap[Vector3, Map[PlanetSideEmpire.Value, Seq[Long]]]()
|
||||||
.addAll(hotSpotLayersOverTime.head.map { entry => (entry.DisplayLocation, Map.empty) })
|
.addAll(
|
||||||
|
hotSpotLayersOverTime.take(1).flatMap { entry =>
|
||||||
|
entry.map { f => (f.DisplayLocation, Map.empty[PlanetSideEmpire.Value, Seq[Long]]) }
|
||||||
|
}
|
||||||
|
)
|
||||||
//note: this pre-seeding of keys allows us to skip a getOrElse call in the foldLeft
|
//note: this pre-seeding of keys allows us to skip a getOrElse call in the foldLeft
|
||||||
hotSpotLayersOverTime.foldLeft(finalMap) { (map, list) =>
|
hotSpotLayersOverTime.foldLeft(finalMap) { (map, list) =>
|
||||||
list.foreach { entry =>
|
list.foreach { entry =>
|
||||||
|
|
@ -153,48 +156,131 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
|
||||||
}.toMap
|
}.toMap
|
||||||
finalMap //explicit for no good reason
|
finalMap //explicit for no good reason
|
||||||
}
|
}
|
||||||
val heatVictorMap = FacilityHackParticipation.diffHeatForFactionMap(regionHeatMapProgression, victorFaction).values
|
val heatMapModifier = FacilityHackParticipation.heatMapComparison(
|
||||||
val heatAgainstMap = FacilityHackParticipation.diffHeatForFactionMap(regionHeatMapProgression, opposingFaction).values
|
FacilityHackParticipation.diffHeatForFactionMap(regionHeatMapProgression, victorFaction).values,
|
||||||
val heatMapModifier = FacilityHackParticipation.heatMapComparison(heatVictorMap, heatAgainstMap)
|
FacilityHackParticipation.diffHeatForFactionMap(regionHeatMapProgression, opposingFaction).values
|
||||||
|
)
|
||||||
heatMapModifier * populationBalanceModifier
|
heatMapModifier * populationBalanceModifier
|
||||||
}
|
}
|
||||||
//5) hack time modifier
|
//4) hack time modifier
|
||||||
val overallTimeMultiplier: Float = if (isResecured) {
|
//Captured major facilities without a lattice link unit and resecured major facilities with a lattice link unit
|
||||||
math.max(0.5f + (hackTime - completionTime) / hackTime, 1f)
|
// incur the full hack time if the module is not transported to a friendly facility
|
||||||
} else {
|
//Captured major facilities with a lattice link unit and resecure major facilities without a lattice link uit
|
||||||
1f
|
// will incur an abbreviated duration
|
||||||
|
val overallTimeMultiplier: Float = {
|
||||||
|
if (
|
||||||
|
building.Faction == PlanetSideEmpire.NEUTRAL ||
|
||||||
|
building.NtuLevel == 0 ||
|
||||||
|
building.Generator.map { _.Condition }.contains(PlanetSideGeneratorState.Destroyed)
|
||||||
|
) { //the facility ran out of nanites or power during the hack or became neutral
|
||||||
|
0f
|
||||||
|
} else if (hasFlag) {
|
||||||
|
if (completionTime >= hackTime) { //hack timed out without llu delivery
|
||||||
|
0.25f
|
||||||
|
} else if (isResecured) {
|
||||||
|
0.5f + (if (hackTime <= completionTime * 0.3f) {
|
||||||
|
completionTime.toFloat / hackTime.toFloat
|
||||||
|
} else if (hackTime >= completionTime * 0.6f) {
|
||||||
|
(hackTime - completionTime).toFloat / hackTime.toFloat
|
||||||
|
} else {
|
||||||
|
0f
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
0.5f + (hackTime - completionTime).toFloat / (2f * hackTime)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isResecured) {
|
||||||
|
0.5f + (hackTime - completionTime).toFloat / (2f * hackTime)
|
||||||
|
} else {
|
||||||
|
0.5f
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//calculate overall command experience points and the individual player multiplier
|
//5. individual contribution factors - by time
|
||||||
|
val contributionPerPlayerByTime = playerContribution.collect {
|
||||||
|
case (a, (_, d, t)) if d >= 600000 && math.abs(completionTime - t) < 5000 =>
|
||||||
|
(a, 0.45f)
|
||||||
|
case (a, (_, d, _)) if d >= 600000 =>
|
||||||
|
(a, 0.25f)
|
||||||
|
case (a, (_, d, t)) if math.abs(completionTime - t) < 5000 =>
|
||||||
|
(a, 0.25f * (0.5f + (d.toFloat / 600000f)))
|
||||||
|
case (a, (_, _, _)) =>
|
||||||
|
(a, 0.15f)
|
||||||
|
}
|
||||||
|
//6. competition bonus
|
||||||
|
//This value will probably suck, and that's fine.
|
||||||
|
val competitionBonus: Long = FacilityHackParticipation.competitionBonus(
|
||||||
|
contributionVictorSize,
|
||||||
|
contributionOpposingSize,
|
||||||
|
steamrollPercentage = 1.25f,
|
||||||
|
steamrollBonus = 5L,
|
||||||
|
overwhelmingOddsPercentage = 0.5f,
|
||||||
|
overwhelmingOddsBonus = 15L
|
||||||
|
)
|
||||||
|
//7. calculate overall command experience points
|
||||||
val finalCep: Long = math.ceil(
|
val finalCep: Long = math.ceil(
|
||||||
math.max(1L, baseExperienceFromFacilityCapture + competitionBonus) *
|
math.max(0L, baseExperienceFromFacilityCapture) *
|
||||||
populationModifier *
|
populationModifier *
|
||||||
competitionMultiplier *
|
competitionMultiplier *
|
||||||
overallTimeMultiplier *
|
overallTimeMultiplier *
|
||||||
Config.app.game.cepRate
|
Config.app.game.experience.cep.rate + competitionBonus
|
||||||
).toLong
|
).toLong
|
||||||
val contributionPerPlayerByTime = playerContribution.collect {
|
//8. reward participants
|
||||||
case (a, (_, d, t)) if d >= 600000 && math.abs(completionTime - t) < 5000 =>
|
//Classically, only players in the SOI are rewarded, and the llu runner too
|
||||||
(a, 1f)
|
val hackerId = hacker.CharId
|
||||||
case (a, (_, d, t)) if math.abs(completionTime - t) < 5000 =>
|
|
||||||
(a, d.toFloat / 6000000f)
|
|
||||||
case (a, (_, d, t)) =>
|
|
||||||
(a, math.max(0, d - completionTime + t).toFloat / 6000000f)
|
|
||||||
}
|
|
||||||
//reward participant(s)
|
|
||||||
// classically, only players in the SOI are rewarded
|
|
||||||
val events = building.Zone.AvatarEvents
|
val events = building.Zone.AvatarEvents
|
||||||
building.PlayersInSOI
|
val playersInSoi = building.PlayersInSOI
|
||||||
.filter { player =>
|
if (playersInSoi.exists(_.CharId == hackerId) && flagCarrier.map(_.CharId).getOrElse(0L) != hackerId) {
|
||||||
player.Faction == victorFaction && player.CharId != hacker.CharId && !flagCarrier.contains(player)
|
events ! AvatarServiceMessage(hacker.Name, AvatarAction.AwardCep(hackerId, finalCep))
|
||||||
}
|
}
|
||||||
|
playersInSoi
|
||||||
|
.filter { player => player.Faction == victorFaction && player.CharId != hackerId }
|
||||||
.foreach { player =>
|
.foreach { player =>
|
||||||
val contributionMultiplier = contributionPerPlayerByTime.getOrElse(player.CharId, 1f)
|
val charId = player.CharId
|
||||||
events ! AvatarServiceMessage(player.Name, AvatarAction.AwardCep(0, (finalCep * contributionMultiplier).toLong))
|
val contributionMultiplier = contributionPerPlayerByTime.getOrElse(charId, 1f)
|
||||||
|
val outputValue = (finalCep * contributionMultiplier).toLong
|
||||||
|
events ! AvatarServiceMessage(player.Name, AvatarAction.AwardCep(0, outputValue))
|
||||||
}
|
}
|
||||||
events ! AvatarServiceMessage(hacker.Name, AvatarAction.AwardCep(hacker.CharId, finalCep))
|
|
||||||
flagCarrier.collect {
|
flagCarrier.collect {
|
||||||
player => events ! AvatarServiceMessage(player.Name, AvatarAction.AwardCep(player.CharId, (finalCep * 0.5f).toLong))
|
case player if !isResecured =>
|
||||||
|
val charId: Long = player.CharId
|
||||||
|
val finalModifiedCep: Long = math.min(
|
||||||
|
(hackTime - completionTime) / 1000L,
|
||||||
|
(finalCep * Config.app.game.experience.cep.lluCarrierModifier).toLong
|
||||||
|
)
|
||||||
|
ToDatabase.reportFacilityCapture(
|
||||||
|
charId,
|
||||||
|
building.Zone.Number,
|
||||||
|
building.GUID.guid,
|
||||||
|
finalModifiedCep,
|
||||||
|
expType="llu"
|
||||||
|
)
|
||||||
|
events ! AvatarServiceMessage(player.Name, AvatarAction.AwardCep(charId, finalModifiedCep))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def eliminateClosestTowerFromParticipating(
|
||||||
|
building: Building,
|
||||||
|
list: Iterable[(UniquePlayer, Float, Seq[Kill])]
|
||||||
|
): Iterable[(UniquePlayer, Float, Seq[Kill])] = {
|
||||||
|
val buildingPosition = building.Position.xy
|
||||||
|
building
|
||||||
|
.Zone
|
||||||
|
.Buildings
|
||||||
|
.values
|
||||||
|
.filter { building => building.BuildingType == StructureType.Tower }
|
||||||
|
.minByOption { tower => Vector3.DistanceSquared(buildingPosition, tower.Position.xy) }
|
||||||
|
.map { tower =>
|
||||||
|
val towerPosition = tower.Position.xy
|
||||||
|
val towerRadius = math.pow(tower.Definition.SOIRadius.toDouble * 0.7d, 2d).toFloat
|
||||||
|
list
|
||||||
|
.map { case (p, f, kills) =>
|
||||||
|
val filteredKills = kills.filter { kill => Vector3.DistanceSquared(kill.victim.Position.xy, towerPosition) <= towerRadius }
|
||||||
|
(p, f, filteredKills)
|
||||||
|
}
|
||||||
|
.filter { case (_, _, kills) => kills.nonEmpty }
|
||||||
|
}
|
||||||
|
.getOrElse(list)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ package net.psforever.objects.serverobject.structures.participation
|
||||||
import net.psforever.objects.serverobject.structures.Building
|
import net.psforever.objects.serverobject.structures.Building
|
||||||
import net.psforever.objects.sourcing.PlayerSource
|
import net.psforever.objects.sourcing.PlayerSource
|
||||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||||
import net.psforever.types.PlanetSideEmpire
|
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
||||||
import net.psforever.util.Config
|
import net.psforever.util.Config
|
||||||
|
|
||||||
final case class TowerHackParticipation(building: Building) extends FacilityHackParticipation {
|
final case class TowerHackParticipation(building: Building) extends FacilityHackParticipation {
|
||||||
|
|
@ -25,8 +25,6 @@ final case class TowerHackParticipation(building: Building) extends FacilityHack
|
||||||
completionTime: Long,
|
completionTime: Long,
|
||||||
isResecured: Boolean
|
isResecured: Boolean
|
||||||
): Unit = {
|
): Unit = {
|
||||||
val curr = System.currentTimeMillis()
|
|
||||||
val hackStart = curr - completionTime
|
|
||||||
val (victorFaction, opposingFaction) = if (!isResecured) {
|
val (victorFaction, opposingFaction) = if (!isResecured) {
|
||||||
(attackingFaction, defenderFaction)
|
(attackingFaction, defenderFaction)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -40,18 +38,10 @@ final case class TowerHackParticipation(building: Building) extends FacilityHack
|
||||||
}
|
}
|
||||||
val contributionVictorSize = contributionVictor.size
|
val contributionVictorSize = contributionVictor.size
|
||||||
if (contributionVictorSize > 0) {
|
if (contributionVictorSize > 0) {
|
||||||
val contributionOpposingSize = contributionOpposing.size
|
|
||||||
//1) experience from killing opposingFaction across duration of hack
|
|
||||||
val baseExperienceFromFacilityCapture: Long = FacilityHackParticipation.calculateExperienceFromKills(
|
|
||||||
building.Position,
|
|
||||||
building.Definition.SOIRadius.toFloat,
|
|
||||||
hackStart,
|
|
||||||
completionTime,
|
|
||||||
opposingFaction,
|
|
||||||
contributionVictor,
|
|
||||||
contributionOpposingSize
|
|
||||||
)
|
|
||||||
//setup for ...
|
//setup for ...
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
val curr = System.currentTimeMillis()
|
||||||
|
val contributionOpposingSize = contributionOpposing.size
|
||||||
val populationIndices = playerPopulationOverTime.indices
|
val populationIndices = playerPopulationOverTime.indices
|
||||||
val allFactions = PlanetSideEmpire.values.filterNot {
|
val allFactions = PlanetSideEmpire.values.filterNot {
|
||||||
_ == PlanetSideEmpire.NEUTRAL
|
_ == PlanetSideEmpire.NEUTRAL
|
||||||
|
|
@ -62,18 +52,74 @@ final case class TowerHackParticipation(building: Building) extends FacilityHack
|
||||||
}.toMap[PlanetSideEmpire.Value, Seq[Int]]
|
}.toMap[PlanetSideEmpire.Value, Seq[Int]]
|
||||||
(individualPopulationByLayer(victorFaction), individualPopulationByLayer(opposingFaction))
|
(individualPopulationByLayer(victorFaction), individualPopulationByLayer(opposingFaction))
|
||||||
}
|
}
|
||||||
|
val soiPlayers = building.PlayersInSOI
|
||||||
|
//1) experience from killing opposingFaction
|
||||||
|
//Because the hack duration of towers is instantaneous, the prior period of five minutes is artificially selected.
|
||||||
|
val baseExperienceFromFacilityCapture: Long = FacilityHackParticipation.calculateExperienceFromKills(
|
||||||
|
FacilityHackParticipation.allocateKillsByPlayers(
|
||||||
|
building.Position,
|
||||||
|
building.Definition.SOIRadius.toFloat,
|
||||||
|
curr - 5.minutes.toMillis,
|
||||||
|
curr,
|
||||||
|
opposingFaction,
|
||||||
|
contributionVictor
|
||||||
|
),
|
||||||
|
contributionOpposingSize
|
||||||
|
)
|
||||||
//2) peak population modifier
|
//2) peak population modifier
|
||||||
|
//Towers should not be regarded as major battles.
|
||||||
|
//As the population rises, the rewards decrease (dramatically).
|
||||||
val populationModifier = FacilityHackParticipation.populationProgressModifier(
|
val populationModifier = FacilityHackParticipation.populationProgressModifier(
|
||||||
opposingPopulationByLayer,
|
victorPopulationByLayer,
|
||||||
{ pop =>
|
{ pop =>
|
||||||
if (pop > 59) 0.75f
|
if (pop > 80) 0f
|
||||||
else if (pop > 29) 0.675f
|
else if (pop > 39) (80 - pop).toFloat * 0.01f
|
||||||
else if (pop > 25) 0.55f
|
else if (pop > 25) 0.5f
|
||||||
else 0.5f
|
else if (pop > 19) 0.55f
|
||||||
|
else if (pop > 9) 0.6f
|
||||||
|
else if (pop > 5) 0.75f
|
||||||
|
else 1f
|
||||||
},
|
},
|
||||||
2
|
2
|
||||||
)
|
)
|
||||||
//3) competition bonus
|
//3) competition multiplier
|
||||||
|
val competitionMultiplier: Float = FacilityHackParticipation.populationBalanceModifier(
|
||||||
|
victorPopulationByLayer,
|
||||||
|
opposingPopulationByLayer,
|
||||||
|
healthyPercentage = 1.25f
|
||||||
|
)
|
||||||
|
//4a. individual contribution factors - by time
|
||||||
|
//Once again, an arbitrary five minute period.
|
||||||
|
val contributionPerPlayerByTime = playerContribution.collect {
|
||||||
|
case (a, (_, d, t)) if d >= 300000 && math.abs(completionTime - t) < 5000 =>
|
||||||
|
(a, 0.45f)
|
||||||
|
case (a, (_, d, _)) if d >= 300000 =>
|
||||||
|
(a, 0.25f)
|
||||||
|
case (a, (_, d, t)) if math.abs(completionTime - t) < 5000 =>
|
||||||
|
(a, 0.25f * (0.5f + (d.toFloat / 300000f)))
|
||||||
|
case (a, (_, _, _)) =>
|
||||||
|
(a, 0.15f)
|
||||||
|
}
|
||||||
|
//4b. individual contribution factors - by distance to goal (secondary_capture)
|
||||||
|
//Because the hack duration of towers is instantaneous, distance from terminal is a more important factor
|
||||||
|
val contributionPerPlayerByDistanceFromGoal = {
|
||||||
|
var minDistance: Float = Float.PositiveInfinity
|
||||||
|
val location = building
|
||||||
|
.CaptureTerminal
|
||||||
|
.map { terminal => terminal.Position }
|
||||||
|
.getOrElse { hacker.Position }
|
||||||
|
soiPlayers
|
||||||
|
.map { p =>
|
||||||
|
val distance = Vector3.Distance(p.Position, location)
|
||||||
|
minDistance = math.min(minDistance, distance)
|
||||||
|
(p.CharId, distance)
|
||||||
|
}
|
||||||
|
.map { case (id, distance) =>
|
||||||
|
(id, math.max(0.15f, minDistance / distance))
|
||||||
|
}
|
||||||
|
}.toMap[Long, Float]
|
||||||
|
//5) token competition bonus
|
||||||
|
//This value will probably suck, and that's fine.
|
||||||
val competitionBonus: Long = FacilityHackParticipation.competitionBonus(
|
val competitionBonus: Long = FacilityHackParticipation.competitionBonus(
|
||||||
contributionVictorSize,
|
contributionVictorSize,
|
||||||
contributionOpposingSize,
|
contributionOpposingSize,
|
||||||
|
|
@ -82,37 +128,29 @@ final case class TowerHackParticipation(building: Building) extends FacilityHack
|
||||||
overwhelmingOddsPercentage = 0.5f,
|
overwhelmingOddsPercentage = 0.5f,
|
||||||
overwhelmingOddsBonus = 30L
|
overwhelmingOddsBonus = 30L
|
||||||
)
|
)
|
||||||
//4) competition multiplier
|
//6. calculate overall command experience points
|
||||||
val competitionMultiplier: Float = FacilityHackParticipation.populationBalanceModifier(
|
|
||||||
victorPopulationByLayer,
|
|
||||||
opposingPopulationByLayer,
|
|
||||||
healthyPercentage = 1.25f
|
|
||||||
)
|
|
||||||
//calculate overall command experience points and the individual player multiplier
|
|
||||||
val finalCep: Long = math.ceil(
|
val finalCep: Long = math.ceil(
|
||||||
math.max(1L, baseExperienceFromFacilityCapture + competitionBonus) *
|
baseExperienceFromFacilityCapture *
|
||||||
populationModifier *
|
populationModifier *
|
||||||
competitionMultiplier *
|
competitionMultiplier *
|
||||||
Config.app.game.cepRate
|
Config.app.game.experience.cep.rate + competitionBonus
|
||||||
).toLong
|
).toLong
|
||||||
val contributionPerPlayerByTime = playerContribution.collect {
|
//7. reward participants
|
||||||
case (a, (_, d, t)) if d >= 300000 && math.abs(completionTime - t) < 5000 =>
|
//Classically, only players in the SOI are rewarded
|
||||||
(a, 0.5f)
|
|
||||||
case (a, (_, d, t)) if math.abs(completionTime - t) < 5000 =>
|
|
||||||
(a, 0.25f * (1f + (d.toFloat / 3000000f)))
|
|
||||||
case (a, (_, d, t)) =>
|
|
||||||
(a, 0.25f * (1f + (math.max(0, d - completionTime + t).toFloat / 3000000f)))
|
|
||||||
}
|
|
||||||
//reward participant(s)
|
|
||||||
// classically, only players in the SOI are rewarded
|
|
||||||
val events = building.Zone.AvatarEvents
|
val events = building.Zone.AvatarEvents
|
||||||
building.PlayersInSOI
|
soiPlayers
|
||||||
.filter { player =>
|
.filter { player =>
|
||||||
player.Faction == victorFaction && player.CharId != hacker.CharId
|
player.Faction == victorFaction && player.CharId != hacker.CharId
|
||||||
}
|
}
|
||||||
.foreach { player =>
|
.foreach { player =>
|
||||||
val contributionMultiplier = contributionPerPlayerByTime.getOrElse(player.CharId, 1f)
|
val charId = player.CharId
|
||||||
events ! AvatarServiceMessage(player.Name, AvatarAction.AwardCep(0, (finalCep * contributionMultiplier).toLong))
|
val contributionTimeMultiplier = contributionPerPlayerByTime.getOrElse(charId, 0.5f)
|
||||||
|
val contributionDistanceMultiplier = contributionPerPlayerByDistanceFromGoal.getOrElse(charId, 0.5f)
|
||||||
|
val outputValue = (finalCep * contributionTimeMultiplier * contributionDistanceMultiplier).toLong
|
||||||
|
events ! AvatarServiceMessage(
|
||||||
|
player.Name,
|
||||||
|
AvatarAction.AwardCep(0, outputValue)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
events ! AvatarServiceMessage(hacker.Name, AvatarAction.AwardCep(hacker.CharId, finalCep))
|
events ! AvatarServiceMessage(hacker.Name, AvatarAction.AwardCep(hacker.CharId, finalCep))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ class FacilityTurretControl(turret: FacilityTurret)
|
||||||
turret.ControlledWeapon(wepNumber = 1).foreach {
|
turret.ControlledWeapon(wepNumber = 1).foreach {
|
||||||
case weapon: Tool =>
|
case weapon: Tool =>
|
||||||
// recharge when last shot fired 3s delay, +1, 200ms interval
|
// recharge when last shot fired 3s delay, +1, 200ms interval
|
||||||
if (weapon.Magazine < weapon.MaxMagazine && System.nanoTime() - weapon.LastDischarge > 3000000000L) {
|
if (weapon.Magazine < weapon.MaxMagazine && System.currentTimeMillis() - weapon.LastDischarge > 3000L) {
|
||||||
weapon.Magazine += 1
|
weapon.Magazine += 1
|
||||||
val seat = turret.Seat(0).get
|
val seat = turret.Seat(0).get
|
||||||
seat.occupant match {
|
seat.occupant match {
|
||||||
|
|
|
||||||
|
|
@ -187,8 +187,10 @@ trait AntTransferBehavior extends TransferBehavior with NtuStorageBehavior {
|
||||||
val chargeToDeposit = if (min == 0) {
|
val chargeToDeposit = if (min == 0) {
|
||||||
transferTarget match {
|
transferTarget match {
|
||||||
case Some(silo: ResourceSilo) =>
|
case Some(silo: ResourceSilo) =>
|
||||||
// Silos would charge from 0-100% in roughly 105s on live (~20%-100% https://youtu.be/veOWToR2nSk?t=1402)
|
scala.math.min(
|
||||||
scala.math.min(scala.math.min(silo.MaxNtuCapacitor / 105, chargeable.NtuCapacitor), max)
|
scala.math.min(silo.MaxNtuCapacitor / silo.Definition.ChargeTime.toMillis.toFloat, chargeable.NtuCapacitor),
|
||||||
|
max
|
||||||
|
)
|
||||||
case _ =>
|
case _ =>
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -893,7 +893,7 @@ object VehicleControl {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if a given activity entry would invalidate the act of charging vehicle shields this tick.
|
* Determine if a given activity entry would invalidate the act of charging vehicle shields this tick.
|
||||||
* @param now the current time (in nanoseconds)
|
* @param now the current time (in milliseconds)
|
||||||
* @param act a `VitalsActivity` entry to test
|
* @param act a `VitalsActivity` entry to test
|
||||||
* @return `true`, if the shield charge would be blocked;
|
* @return `true`, if the shield charge would be blocked;
|
||||||
* `false`, otherwise
|
* `false`, otherwise
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import net.psforever.objects.vital.etc.{ExplodingEntityReason, PainboxReason, Su
|
||||||
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
|
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
|
||||||
import net.psforever.objects.vital.projectile.ProjectileReason
|
import net.psforever.objects.vital.projectile.ProjectileReason
|
||||||
import net.psforever.types.{ExoSuitType, ImplantType, TransactionType}
|
import net.psforever.types.{ExoSuitType, ImplantType, TransactionType}
|
||||||
|
import net.psforever.util.Config
|
||||||
|
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
|
|
||||||
|
|
@ -327,7 +328,7 @@ trait InGameHistory {
|
||||||
}
|
}
|
||||||
|
|
||||||
def GetContribution(): Option[List[InGameActivity]] = {
|
def GetContribution(): Option[List[InGameActivity]] = {
|
||||||
Option(GetContributionDuringPeriod(History, duration = 600000L))
|
Option(GetContributionDuringPeriod(History, duration = Config.app.game.experience.longContributionTime))
|
||||||
}
|
}
|
||||||
|
|
||||||
def GetContributionDuringPeriod(list: List[InGameActivity], duration: Long): List[InGameActivity] = {
|
def GetContributionDuringPeriod(list: List[InGameActivity], duration: Long): List[InGameActivity] = {
|
||||||
|
|
|
||||||
|
|
@ -78,23 +78,23 @@ object KillAssists {
|
||||||
lastDamage: Option[DamageResult],
|
lastDamage: Option[DamageResult],
|
||||||
history: Iterable[InGameActivity],
|
history: Iterable[InGameActivity],
|
||||||
): Seq[(PlayerSource, KDAStat)] = {
|
): Seq[(PlayerSource, KDAStat)] = {
|
||||||
val shortHistory = limitHistoryToThisLife(history.toList)
|
val truncatedHistory = limitHistoryToThisLife(history.toList)
|
||||||
determineKiller(lastDamage, shortHistory) match {
|
determineKiller(lastDamage, truncatedHistory) match {
|
||||||
case Some((result, killer: PlayerSource)) =>
|
case Some((result, killer: PlayerSource)) =>
|
||||||
val assists = collectKillAssistsForPlayer(victim, shortHistory, Some(killer))
|
val assists = collectKillAssistsForPlayer(victim, truncatedHistory, Some(killer))
|
||||||
val fullBep = calculateExperience(killer, victim, shortHistory)
|
val fullBep = calculateExperience(killer, victim, truncatedHistory)
|
||||||
val hitSquad = (killer, Kill(victim, result, fullBep)) +: assists.map {
|
val hitSquad = (killer, Kill(victim, result, fullBep)) +: assists.map {
|
||||||
case ContributionStatsOutput(p, w, r) => (p, Assist(victim, w, r, (fullBep * r).toLong))
|
case ContributionStatsOutput(p, w, r) => (p, Assist(victim, w, r, (fullBep * r).toLong))
|
||||||
}.toSeq
|
}.toSeq
|
||||||
(victim, Death(hitSquad.map { _._1 }, shortHistory.last.time - shortHistory.head.time, fullBep)) +: hitSquad
|
(victim, Death(hitSquad.map { _._1 }, truncatedHistory.last.time - truncatedHistory.head.time, fullBep)) +: hitSquad
|
||||||
|
|
||||||
case _ =>
|
case _ =>
|
||||||
val assists = collectKillAssistsForPlayer(victim, shortHistory, None)
|
val assists = collectKillAssistsForPlayer(victim, truncatedHistory, None)
|
||||||
val fullBep = Support.baseExperience(victim, shortHistory)
|
val fullBep = Support.baseExperience(victim, truncatedHistory)
|
||||||
val hitSquad = assists.map {
|
val hitSquad = assists.map {
|
||||||
case ContributionStatsOutput(p, w, r) => (p, Assist(victim, w, r, (fullBep * r).toLong))
|
case ContributionStatsOutput(p, w, r) => (p, Assist(victim, w, r, (fullBep * r).toLong))
|
||||||
}.toSeq
|
}.toSeq
|
||||||
(victim, Death(hitSquad.map { _._1 }, shortHistory.last.time - shortHistory.head.time, fullBep)) +: hitSquad
|
(victim, Death(hitSquad.map { _._1 }, truncatedHistory.last.time - truncatedHistory.head.time, fullBep)) +: hitSquad
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import net.psforever.objects.vital.projectile.ProjectileReason
|
||||||
import net.psforever.objects.zones.exp.rec.{CombinedHealthAndArmorContributionProcess, MachineRecoveryExperienceContributionProcess}
|
import net.psforever.objects.zones.exp.rec.{CombinedHealthAndArmorContributionProcess, MachineRecoveryExperienceContributionProcess}
|
||||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||||
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
||||||
|
import net.psforever.util.Config
|
||||||
|
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
|
|
||||||
|
|
@ -49,7 +50,7 @@ object KillContributions {
|
||||||
multivehicle_rearm_terminal,
|
multivehicle_rearm_terminal,
|
||||||
lodestar_repair_terminal
|
lodestar_repair_terminal
|
||||||
).collect { _.ObjectId }
|
).collect { _.ObjectId }
|
||||||
} //TODO currently includes things that are not typical items but are used for expressing contribution implements
|
} //TODO currently includes things that are not typical equipment but things that express contribution
|
||||||
|
|
||||||
/** cached for empty collection returns; please do not add anything to it */
|
/** cached for empty collection returns; please do not add anything to it */
|
||||||
private val emptyMap: mutable.LongMap[ContributionStats] = mutable.LongMap.empty[ContributionStats]
|
private val emptyMap: mutable.LongMap[ContributionStats] = mutable.LongMap.empty[ContributionStats]
|
||||||
|
|
@ -119,21 +120,20 @@ object KillContributions {
|
||||||
): Iterable[(Long, ContributionStatsOutput)] = {
|
): Iterable[(Long, ContributionStatsOutput)] = {
|
||||||
val faction = target.Faction
|
val faction = target.Faction
|
||||||
/*
|
/*
|
||||||
divide into applicable time periods - long for 10 minutes and short 5 minutes;
|
divide into applicable time periods;
|
||||||
these two periods represent passes over the in-game history to evaluate statistic modification events;
|
these two periods represent passes over the in-game history to evaluate statistic modification events;
|
||||||
the short time period should stand on its own, but should also be represented in the long time period;
|
the short time period should stand on its own, but should also be represented in the long time period;
|
||||||
more players should be rewarded if one qualifies for the longer time period's evaluation
|
more players should be rewarded if one qualifies for the longer time period's evaluation
|
||||||
*/
|
*/
|
||||||
//divide by applicable time periods (long=10minutes, short=5minutes)
|
val (contributions, (longHistory, shortHistory)) = {
|
||||||
val (contributions, (shortHistory, longHistory)) = {
|
|
||||||
val killTime = kill.time.toDate.getTime
|
val killTime = kill.time.toDate.getTime
|
||||||
val shortPeriod = killTime - 300000L
|
val shortPeriod = killTime - Config.app.game.experience.shortContributionTime
|
||||||
val (contrib, onlyHistory) = history.partition { _.isInstanceOf[Contribution] }
|
val (contrib, onlyHistory) = history.partition { _.isInstanceOf[Contribution] }
|
||||||
(
|
(
|
||||||
contrib
|
contrib
|
||||||
.collect { case Contribution(unique, entries) => (unique, entries) }
|
.collect { case Contribution(unique, entries) => (unique, entries) }
|
||||||
.toMap[SourceUniqueness, List[InGameActivity]],
|
.toMap[SourceUniqueness, List[InGameActivity]],
|
||||||
limitHistoryToThisLife(onlyHistory.toList, killTime).partition { _.time > shortPeriod }
|
limitHistoryToThisLife(onlyHistory.toList, killTime).partition { _.time < shortPeriod }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
//events that are older than 5 minutes are enough to prove one has been alive that long
|
//events that are older than 5 minutes are enough to prove one has been alive that long
|
||||||
|
|
@ -195,7 +195,7 @@ object KillContributions {
|
||||||
* @return the potentially truncated history
|
* @return the potentially truncated history
|
||||||
*/
|
*/
|
||||||
private def limitHistoryToThisLife(history: List[InGameActivity], eventTime: Long): List[InGameActivity] = {
|
private def limitHistoryToThisLife(history: List[InGameActivity], eventTime: Long): List[InGameActivity] = {
|
||||||
limitHistoryToThisLife(history, eventTime, eventTime - 600000L)
|
limitHistoryToThisLife(history, eventTime, eventTime - Config.app.game.experience.longContributionTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -262,9 +262,9 @@ object KillContributions {
|
||||||
excludedTargets: mutable.ListBuffer[SourceUniqueness]
|
excludedTargets: mutable.ListBuffer[SourceUniqueness]
|
||||||
): mutable.LongMap[ContributionStats] = {
|
): mutable.LongMap[ContributionStats] = {
|
||||||
contributeWithRevivalActivity(history, existingParticipants)
|
contributeWithRevivalActivity(history, existingParticipants)
|
||||||
contributeWithTerminalActivity(faction, history, contributions, excludedTargets, existingParticipants)
|
contributeWithTerminalActivity(history, faction, contributions, excludedTargets, existingParticipants)
|
||||||
contributeWithVehicleTransportActivity(history, faction, contributions, excludedTargets, existingParticipants)
|
contributeWithVehicleTransportActivity(kill, history, faction, contributions, excludedTargets, existingParticipants)
|
||||||
contributeWithVehicleCargoTransportActivity(history, faction, contributions, excludedTargets, existingParticipants)
|
contributeWithVehicleCargoTransportActivity(kill, history, faction, contributions, excludedTargets, existingParticipants)
|
||||||
contributeWithKillWhileMountedActivity(kill, faction, contributions, excludedTargets, existingParticipants)
|
contributeWithKillWhileMountedActivity(kill, faction, contributions, excludedTargets, existingParticipants)
|
||||||
existingParticipants.remove(0)
|
existingParticipants.remove(0)
|
||||||
existingParticipants
|
existingParticipants
|
||||||
|
|
@ -314,13 +314,17 @@ object KillContributions {
|
||||||
excludedTargets.addOne(owner)
|
excludedTargets.addOne(owner)
|
||||||
excludedTargets.addOne(attacker.unique)
|
excludedTargets.addOne(attacker.unique)
|
||||||
val time = kill.time.toDate.getTime
|
val time = kill.time.toDate.getTime
|
||||||
|
val weaponStat = Support.calculateSupportExperience(
|
||||||
|
event = "mounted-kill",
|
||||||
|
WeaponStats(DriverAssist(mount.Definition.ObjectId), 1, 1, time, 1f)
|
||||||
|
)
|
||||||
combineStatsInto(
|
combineStatsInto(
|
||||||
out,
|
out,
|
||||||
(
|
(
|
||||||
owner.charId,
|
owner.charId,
|
||||||
ContributionStats(
|
ContributionStats(
|
||||||
PlayerSource(owner, mount.Position),
|
PlayerSource(owner, mount.Position),
|
||||||
Seq(WeaponStats(DriverAssist(mount.Definition.ObjectId), 1, 1, time, 10f)),
|
Seq(weaponStat),
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
|
|
@ -332,12 +336,12 @@ object KillContributions {
|
||||||
}
|
}
|
||||||
combineStatsInto(
|
combineStatsInto(
|
||||||
out,
|
out,
|
||||||
extractContributionsForMachineByTarget(mount, faction, eventTime, contributions, excludedTargets)
|
extractContributionsForMachineByTarget(mount, faction, eventTime, contributions, excludedTargets, eventOutputType="support-repair")
|
||||||
)
|
)
|
||||||
case Some((mount: TurretSource, _: PlayerSource)) if !excludedTargets.contains(mount.unique) =>
|
case Some((mount: TurretSource, _: PlayerSource)) if !excludedTargets.contains(mount.unique) =>
|
||||||
combineStatsInto(
|
combineStatsInto(
|
||||||
out,
|
out,
|
||||||
extractContributionsForMachineByTarget(mount, faction, eventTime, contributions, excludedTargets)
|
extractContributionsForMachineByTarget(mount, faction, eventTime, contributions, excludedTargets, eventOutputType="support-repair-turret")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -345,6 +349,7 @@ object KillContributions {
|
||||||
/**
|
/**
|
||||||
* Gather and reward specific in-game equipment use activity.<br>
|
* Gather and reward specific in-game equipment use activity.<br>
|
||||||
* na
|
* na
|
||||||
|
* @param kill the in-game event that maintains information about the other player's death
|
||||||
* @param history chronology of activity the game considers noteworthy
|
* @param history chronology of activity the game considers noteworthy
|
||||||
* @param faction empire to target
|
* @param faction empire to target
|
||||||
* @param contributions mapping between external entities
|
* @param contributions mapping between external entities
|
||||||
|
|
@ -356,6 +361,7 @@ object KillContributions {
|
||||||
* @see `extractContributionsForMachineByTarget`
|
* @see `extractContributionsForMachineByTarget`
|
||||||
*/
|
*/
|
||||||
private def contributeWithVehicleTransportActivity(
|
private def contributeWithVehicleTransportActivity(
|
||||||
|
kill: Kill,
|
||||||
history: List[InGameActivity],
|
history: List[InGameActivity],
|
||||||
faction: PlanetSideEmpire.Value,
|
faction: PlanetSideEmpire.Value,
|
||||||
contributions: Map[SourceUniqueness, List[InGameActivity]],
|
contributions: Map[SourceUniqueness, List[InGameActivity]],
|
||||||
|
|
@ -385,11 +391,19 @@ object KillContributions {
|
||||||
}
|
}
|
||||||
} || {
|
} || {
|
||||||
val sameZone = in.zoneNumber == out.zoneNumber
|
val sameZone = in.zoneNumber == out.zoneNumber
|
||||||
val distanceMoved = Vector3.DistanceSquared(in.vehicle.Position.xy, out.vehicle.Position.xy)
|
val distanceTransported = Vector3.DistanceSquared(in.vehicle.Position.xy, out.vehicle.Position.xy)
|
||||||
|
val distanceMoved = {
|
||||||
|
val killLocation = kill.info.adversarial
|
||||||
|
.collect { adversarial => adversarial.attacker.Position.xy }
|
||||||
|
.getOrElse(Vector3.Zero)
|
||||||
|
Vector3.DistanceSquared(killLocation, out.player.Position.xy)
|
||||||
|
}
|
||||||
val timeSpent = out.time - in.time
|
val timeSpent = out.time - in.time
|
||||||
timeSpent >= 210000 /* 3:30 */ ||
|
distanceMoved < 5625f /* 75m */ &&
|
||||||
(sameZone && (distanceMoved > 160000f || distanceMoved > 10000f && timeSpent >= 60000)) |
|
(timeSpent >= 210000L /* 3:30 */ ||
|
||||||
(!sameZone && (distanceMoved > 10000f || timeSpent >= 120000))
|
(sameZone && (distanceTransported > 160000f /* 400m */ ||
|
||||||
|
distanceTransported > 10000f /* 100m */ && timeSpent >= 60000L /* 1:00m */)) ||
|
||||||
|
(!sameZone && (distanceTransported > 10000f /* 100m */ || timeSpent >= 120000L /* 2:00 */ )))
|
||||||
}) =>
|
}) =>
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
@ -398,18 +412,23 @@ object KillContributions {
|
||||||
.groupBy { _.vehicle }
|
.groupBy { _.vehicle }
|
||||||
.collect { case (mount, dismountsFromVehicle) if mount.owner.nonEmpty =>
|
.collect { case (mount, dismountsFromVehicle) if mount.owner.nonEmpty =>
|
||||||
val promotedOwner = PlayerSource(mount.owner.get, mount.Position)
|
val promotedOwner = PlayerSource(mount.owner.get, mount.Position)
|
||||||
val equipmentUseContext = mount.Definition match {
|
val (equipmentUseContext, equipmentUseEvent) = mount.Definition match {
|
||||||
case v @ GlobalDefinitions.router =>
|
case v @ GlobalDefinitions.router =>
|
||||||
RouterKillAssist(v.ObjectId)
|
(RouterKillAssist(v.ObjectId), "router")
|
||||||
case v =>
|
case v =>
|
||||||
HotDropKillAssist(v.ObjectId, 0)
|
(HotDropKillAssist(v.ObjectId, 0), "hotdrop")
|
||||||
}
|
}
|
||||||
val statContext = Seq(WeaponStats(equipmentUseContext, 0, dismountsFromVehicle.size, dismountsFromVehicle.maxBy(_.time).time, 15f))
|
val size = dismountsFromVehicle.size
|
||||||
|
val time = dismountsFromVehicle.maxBy(_.time).time
|
||||||
|
val weaponStat = Support.calculateSupportExperience(
|
||||||
|
equipmentUseEvent,
|
||||||
|
WeaponStats(equipmentUseContext, size, size, time, 1f)
|
||||||
|
)
|
||||||
combineStatsInto(
|
combineStatsInto(
|
||||||
out,
|
out,
|
||||||
(
|
(
|
||||||
promotedOwner.CharId,
|
promotedOwner.CharId,
|
||||||
ContributionStats(promotedOwner, statContext, 1, 1, 1, statContext.head.time)
|
ContributionStats(promotedOwner, Seq(weaponStat), size, size, size, time)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
contributions.get(mount.unique).collect {
|
contributions.get(mount.unique).collect {
|
||||||
|
|
@ -417,13 +436,13 @@ object KillContributions {
|
||||||
val mountHistory = dismountsFromVehicle
|
val mountHistory = dismountsFromVehicle
|
||||||
.flatMap { event =>
|
.flatMap { event =>
|
||||||
val eventTime = event.time
|
val eventTime = event.time
|
||||||
val startTime = event.pairedEvent.get.time - 600000L
|
val startTime = event.pairedEvent.get.time - Config.app.game.experience.longContributionTime
|
||||||
limitHistoryToThisLife(list, eventTime, startTime)
|
limitHistoryToThisLife(list, eventTime, startTime)
|
||||||
}
|
}
|
||||||
.distinctBy(_.time)
|
.distinctBy(_.time)
|
||||||
combineStatsInto(
|
combineStatsInto(
|
||||||
out,
|
out,
|
||||||
extractContributionsForMachineByTarget(mount, faction, mountHistory, contributions, excludedTargets)
|
extractContributionsForMachineByTarget(mount, faction, mountHistory, contributions, excludedTargets, eventOutputType="support-repair")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -432,6 +451,7 @@ object KillContributions {
|
||||||
/**
|
/**
|
||||||
* Gather and reward specific in-game equipment use activity.<br>
|
* Gather and reward specific in-game equipment use activity.<br>
|
||||||
* na
|
* na
|
||||||
|
* @param kill the in-game event that maintains information about the other player's death
|
||||||
* @param faction empire to target
|
* @param faction empire to target
|
||||||
* @param contributions mapping between external entities
|
* @param contributions mapping between external entities
|
||||||
* the target has interacted with in the form of in-game activity
|
* the target has interacted with in the form of in-game activity
|
||||||
|
|
@ -442,6 +462,7 @@ object KillContributions {
|
||||||
* @see `extractContributionsForMachineByTarget`
|
* @see `extractContributionsForMachineByTarget`
|
||||||
*/
|
*/
|
||||||
private def contributeWithVehicleCargoTransportActivity(
|
private def contributeWithVehicleCargoTransportActivity(
|
||||||
|
kill: Kill,
|
||||||
history: List[InGameActivity],
|
history: List[InGameActivity],
|
||||||
faction: PlanetSideEmpire.Value,
|
faction: PlanetSideEmpire.Value,
|
||||||
contributions: Map[SourceUniqueness, List[InGameActivity]],
|
contributions: Map[SourceUniqueness, List[InGameActivity]],
|
||||||
|
|
@ -463,9 +484,16 @@ object KillContributions {
|
||||||
if in.vehicle.unique == out.vehicle.unique &&
|
if in.vehicle.unique == out.vehicle.unique &&
|
||||||
out.vehicle.Faction == out.cargo.Faction &&
|
out.vehicle.Faction == out.cargo.Faction &&
|
||||||
(in.vehicle.Definition == GlobalDefinitions.router || {
|
(in.vehicle.Definition == GlobalDefinitions.router || {
|
||||||
val distanceMoved = Vector3.DistanceSquared(in.vehicle.Position.xy, out.vehicle.Position.xy)
|
val distanceTransported = Vector3.DistanceSquared(in.vehicle.Position.xy, out.vehicle.Position.xy)
|
||||||
|
val distanceMoved = {
|
||||||
|
val killLocation = kill.info.adversarial
|
||||||
|
.collect { adversarial => adversarial.attacker.Position.xy }
|
||||||
|
.getOrElse(Vector3.Zero)
|
||||||
|
Vector3.DistanceSquared(killLocation, out.cargo.Position.xy)
|
||||||
|
}
|
||||||
val timeSpent = out.time - in.time
|
val timeSpent = out.time - in.time
|
||||||
timeSpent >= 210000 /* 3:30 */ || distanceMoved > 640000f
|
distanceMoved < 5625f /* 75m */ &&
|
||||||
|
(timeSpent >= 210000 /* 3:30 */ || distanceTransported > 360000f /* 600m */)
|
||||||
}) =>
|
}) =>
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
@ -478,7 +506,13 @@ object KillContributions {
|
||||||
dismountsFromVehicle
|
dismountsFromVehicle
|
||||||
.groupBy(_.vehicle)
|
.groupBy(_.vehicle)
|
||||||
.map { case (vehicle, events) =>
|
.map { case (vehicle, events) =>
|
||||||
(vehicle, vehicle.owner, Seq(WeaponStats(HotDropKillAssist(vehicle.Definition.ObjectId, mountId), 0, events.size, events.maxBy(_.time).time, 15f)))
|
val size = events.size
|
||||||
|
val time = events.maxBy(_.time).time
|
||||||
|
val weaponStat = Support.calculateSupportExperience(
|
||||||
|
event = "hotdrop",
|
||||||
|
WeaponStats(HotDropKillAssist(vehicle.Definition.ObjectId, mountId), size, size, time, 1f)
|
||||||
|
)
|
||||||
|
(vehicle, vehicle.owner, Seq(weaponStat))
|
||||||
}
|
}
|
||||||
.collect { case (vehicle, Some(owner), statContext) =>
|
.collect { case (vehicle, Some(owner), statContext) =>
|
||||||
combineStatsInto(
|
combineStatsInto(
|
||||||
|
|
@ -493,13 +527,13 @@ object KillContributions {
|
||||||
val mountHistory = dismountsFromVehicle
|
val mountHistory = dismountsFromVehicle
|
||||||
.flatMap { event =>
|
.flatMap { event =>
|
||||||
val eventTime = event.time
|
val eventTime = event.time
|
||||||
val startTime = event.pairedEvent.get.time - 600000L
|
val startTime = event.pairedEvent.get.time - Config.app.game.experience.longContributionTime
|
||||||
limitHistoryToThisLife(list, eventTime, startTime)
|
limitHistoryToThisLife(list, eventTime, startTime)
|
||||||
}
|
}
|
||||||
.distinctBy(_.time)
|
.distinctBy(_.time)
|
||||||
combineStatsInto(
|
combineStatsInto(
|
||||||
out,
|
out,
|
||||||
extractContributionsForMachineByTarget(mount, faction, mountHistory, contributions, excludedTargets)
|
extractContributionsForMachineByTarget(mount, faction, mountHistory, contributions, excludedTargets, eventOutputType="support-repair")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
contributions.get(vehicle.unique).collect {
|
contributions.get(vehicle.unique).collect {
|
||||||
|
|
@ -507,13 +541,13 @@ object KillContributions {
|
||||||
val carrierHistory = dismountsFromVehicle
|
val carrierHistory = dismountsFromVehicle
|
||||||
.flatMap { event =>
|
.flatMap { event =>
|
||||||
val eventTime = event.time
|
val eventTime = event.time
|
||||||
val startTime = event.pairedEvent.get.time - 600000L
|
val startTime = event.pairedEvent.get.time - Config.app.game.experience.longContributionTime
|
||||||
limitHistoryToThisLife(list, eventTime, startTime)
|
limitHistoryToThisLife(list, eventTime, startTime)
|
||||||
}
|
}
|
||||||
.distinctBy(_.time)
|
.distinctBy(_.time)
|
||||||
combineStatsInto(
|
combineStatsInto(
|
||||||
out,
|
out,
|
||||||
extractContributionsForMachineByTarget(vehicle, faction, carrierHistory, contributions, excludedTargets)
|
extractContributionsForMachineByTarget(vehicle, faction, carrierHistory, contributions, excludedTargets, eventOutputType="support-repair")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -542,104 +576,120 @@ object KillContributions {
|
||||||
* @see `TerminalUsedActivity`
|
* @see `TerminalUsedActivity`
|
||||||
*/
|
*/
|
||||||
private def contributeWithTerminalActivity(
|
private def contributeWithTerminalActivity(
|
||||||
faction: PlanetSideEmpire.Value,
|
|
||||||
history: List[InGameActivity],
|
history: List[InGameActivity],
|
||||||
|
faction: PlanetSideEmpire.Value,
|
||||||
contributions: Map[SourceUniqueness, List[InGameActivity]],
|
contributions: Map[SourceUniqueness, List[InGameActivity]],
|
||||||
excludedTargets: mutable.ListBuffer[SourceUniqueness],
|
excludedTargets: mutable.ListBuffer[SourceUniqueness],
|
||||||
out: mutable.LongMap[ContributionStats]
|
out: mutable.LongMap[ContributionStats]
|
||||||
): Unit = {
|
): Unit = {
|
||||||
val data = history
|
history
|
||||||
.collect {
|
.collect {
|
||||||
case h: HealFromTerminal => (h.term, (h, h.term.hacked))
|
case h: HealFromTerminal => (h.term, h)
|
||||||
case r: RepairFromTerminal => (r.term, (r, r.term.hacked))
|
case r: RepairFromTerminal => (r.term, r)
|
||||||
case t: TerminalUsedActivity => (t.terminal, (t, t.terminal.hacked))
|
case t: TerminalUsedActivity => (t.terminal, t)
|
||||||
}
|
}
|
||||||
.groupBy(_._1.unique)
|
.groupBy(_._1.unique)
|
||||||
.map { case (_, list) => (list.head._1, list.map { _._2 }) }
|
.map {
|
||||||
data.flatMap {
|
case (_, events1) =>
|
||||||
case (terminal, events) =>
|
val (termThings1, _) = events1.unzip
|
||||||
val (activity, hackState) = events.unzip
|
val hackContext = HackKillAssist(GlobalDefinitions.remote_electronics_kit.ObjectId, termThings1.head.Definition.ObjectId)
|
||||||
val terminalFaction = terminal.Faction
|
if (termThings1.exists(t => t.Faction != faction && t.hacked.nonEmpty)) {
|
||||||
if (terminalFaction != faction && hackState.exists { _.nonEmpty }) {
|
/*
|
||||||
/*
|
if the terminal has been hacked,
|
||||||
if the terminal has been hacked,
|
and the original terminal does not align with our own faction,
|
||||||
and the original terminal does not align with our own faction,
|
then the support must be reported as a hack;
|
||||||
then the support must be reported as a hack;
|
if we are the same faction as the terminal, then the hacked condition is irrelevant
|
||||||
if we are the same faction as the terminal, then the hacked condition is irrelevant
|
*/
|
||||||
*/
|
events1
|
||||||
val hackContext = HackKillAssist(GlobalDefinitions.remote_electronics_kit.ObjectId, terminal.Definition.ObjectId)
|
.collect { case out @ (t, _) if t.hacked.nonEmpty => out }
|
||||||
hackState
|
.groupBy { case (t, _) => t.hacked.get.player.unique }
|
||||||
.groupBy(_.get.player)
|
.foreach { case (_, events2) =>
|
||||||
.collect {
|
val (termThings2, events3) = events2.unzip
|
||||||
case (player, _) if player.Faction == faction => //only reward allied hacking
|
val hacker = termThings2.head.hacked.get.player
|
||||||
val time = activity.maxBy(_.time).time
|
val size = events3.size
|
||||||
|
val time = events3.maxBy(_.time).time
|
||||||
|
val weaponStats = Support.calculateSupportExperience(
|
||||||
|
event = "hack",
|
||||||
|
WeaponStats(hackContext, size, size, time, 1f)
|
||||||
|
)
|
||||||
combineStatsInto(
|
combineStatsInto(
|
||||||
out,
|
out,
|
||||||
(
|
(
|
||||||
player.CharId,
|
hacker.CharId,
|
||||||
ContributionStats(
|
ContributionStats(
|
||||||
player,
|
hacker,
|
||||||
Seq(WeaponStats(hackContext, 0, 1, time, 10f)),
|
Seq(weaponStats),
|
||||||
0,
|
size,
|
||||||
0,
|
size,
|
||||||
1,
|
size,
|
||||||
time
|
time
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
|
||||||
activity
|
|
||||||
} else if (terminalFaction == faction) {
|
|
||||||
val eventTime = activity.maxBy(_.time).time
|
|
||||||
val startTime = activity.minBy(_.time).time - 600000L
|
|
||||||
val (equipmentUseContext, ownerOpt) = terminal.installation match {
|
|
||||||
case v: VehicleSource =>
|
|
||||||
terminal.Definition match {
|
|
||||||
case GlobalDefinitions.order_terminala =>
|
|
||||||
combineStatsInto(out, extractContributionsForMachineByTarget(v, faction, eventTime, startTime, contributions, excludedTargets))
|
|
||||||
(AmsResupplyKillAssist(terminal.Definition.ObjectId), v.owner)
|
|
||||||
case GlobalDefinitions.order_terminalb =>
|
|
||||||
combineStatsInto(out, extractContributionsForMachineByTarget(v, faction, eventTime, startTime, contributions, excludedTargets))
|
|
||||||
(AmsResupplyKillAssist(terminal.Definition.ObjectId), v.owner)
|
|
||||||
case GlobalDefinitions.lodestar_repair_terminal =>
|
|
||||||
combineStatsInto(out, extractContributionsForMachineByTarget(v, faction, eventTime, startTime, contributions, excludedTargets))
|
|
||||||
(RepairKillAssist(terminal.Definition.ObjectId, v.Definition.ObjectId), v.owner)
|
|
||||||
case GlobalDefinitions.bfr_rearm_terminal =>
|
|
||||||
combineStatsInto(out, extractContributionsForMachineByTarget(v, faction, eventTime, startTime, contributions, excludedTargets))
|
|
||||||
(LodestarRearmKillAssist(terminal.Definition.ObjectId), v.owner)
|
|
||||||
case GlobalDefinitions.multivehicle_rearm_terminal =>
|
|
||||||
combineStatsInto(out, extractContributionsForMachineByTarget(v, faction, eventTime, startTime, contributions, excludedTargets))
|
|
||||||
(LodestarRearmKillAssist(terminal.Definition.ObjectId), v.owner)
|
|
||||||
case _ =>
|
|
||||||
(NoUse(), None)
|
|
||||||
}
|
}
|
||||||
case _: BuildingSource =>
|
} else if (termThings1.exists(_.Faction == faction)) {
|
||||||
combineStatsInto(out, extractContributionsForMachineByTarget(terminal, faction, eventTime, startTime, contributions, excludedTargets))
|
//faction-aligned terminal
|
||||||
(NoUse(), None) //general terminal use
|
val (_, events2) = events1.unzip
|
||||||
case _ =>
|
val eventTime = events2.maxBy(_.time).time
|
||||||
(NoUse(), None)
|
val startTime = events2.minBy(_.time).time - Config.app.game.experience.longContributionTime
|
||||||
}
|
val termThingsHead = termThings1.head
|
||||||
ownerOpt.collect { owner =>
|
val (equipmentUseContext, equipmentUseEvent, installationEvent, target) = termThingsHead.installation match {
|
||||||
val time = activity.maxBy(_.time).time
|
case v: VehicleSource =>
|
||||||
combineStatsInto(
|
termThingsHead.Definition match {
|
||||||
out,
|
case GlobalDefinitions.order_terminala =>
|
||||||
(
|
(AmsResupplyKillAssist(GlobalDefinitions.order_terminala.ObjectId), "ams-resupply", "support-repair", Some(v))
|
||||||
owner.charId,
|
case GlobalDefinitions.order_terminalb =>
|
||||||
ContributionStats(
|
(AmsResupplyKillAssist(GlobalDefinitions.order_terminalb.ObjectId), "ams-resupply", "support-repair", Some(v))
|
||||||
PlayerSource(owner, terminal.installation.Position),
|
case GlobalDefinitions.lodestar_repair_terminal =>
|
||||||
Seq(WeaponStats(equipmentUseContext, 0, 1, time, 10f)),
|
(RepairKillAssist(GlobalDefinitions.lodestar_repair_terminal.ObjectId, v.Definition.ObjectId), "lodestar-repair", "support-repair", Some(v))
|
||||||
0,
|
case GlobalDefinitions.bfr_rearm_terminal =>
|
||||||
0,
|
(LodestarRearmKillAssist(GlobalDefinitions.bfr_rearm_terminal.ObjectId), "lodestar-rearm", "support-repair", Some(v))
|
||||||
1,
|
case GlobalDefinitions.multivehicle_rearm_terminal =>
|
||||||
time
|
(LodestarRearmKillAssist(GlobalDefinitions.multivehicle_rearm_terminal.ObjectId), "lodestar-rearm", "support-repair", Some(v))
|
||||||
|
case _ =>
|
||||||
|
(NoUse(), "", "", None)
|
||||||
|
}
|
||||||
|
case _: BuildingSource =>
|
||||||
|
(NoUse(), "", "support-repair-terminal", Some(termThingsHead))
|
||||||
|
case _ =>
|
||||||
|
(NoUse(), "", "", None)
|
||||||
|
}
|
||||||
|
target.map { src =>
|
||||||
|
combineStatsInto(
|
||||||
|
out,
|
||||||
|
extractContributionsForMachineByTarget(src, faction, eventTime, startTime, contributions, excludedTargets, installationEvent)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
events1
|
||||||
|
.map { case (a, b) => (a.installation, b) }
|
||||||
|
.collect { case (installation: VehicleSource, evt) if installation.owner.nonEmpty => (installation, evt) }
|
||||||
|
.groupBy(_._1.owner.get)
|
||||||
|
.collect { case (owner, list) =>
|
||||||
|
val (installations, events2) = list.unzip
|
||||||
|
val size = events2.size
|
||||||
|
val time = events2.maxBy(_.time).time
|
||||||
|
val weaponStats = Support.calculateSupportExperience(
|
||||||
|
equipmentUseEvent,
|
||||||
|
WeaponStats(equipmentUseContext, size, size, time, 1f)
|
||||||
)
|
)
|
||||||
))
|
combineStatsInto(
|
||||||
|
out,
|
||||||
|
(
|
||||||
|
owner.charId,
|
||||||
|
ContributionStats(
|
||||||
|
PlayerSource(owner, installations.head.Position),
|
||||||
|
Seq(weaponStats),
|
||||||
|
size,
|
||||||
|
size,
|
||||||
|
size,
|
||||||
|
time
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
activity
|
None
|
||||||
} else {
|
}
|
||||||
Nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -671,7 +721,12 @@ object KillContributions {
|
||||||
id,
|
id,
|
||||||
ContributionStats(
|
ContributionStats(
|
||||||
user,
|
user,
|
||||||
Seq(WeaponStats(ReviveKillAssist(objectId), 100, 1, time, math.log(eventSize + 1).toFloat * 15f)),
|
Seq({
|
||||||
|
Support.calculateSupportExperience(
|
||||||
|
event = "revival",
|
||||||
|
WeaponStats(ReviveKillAssist(objectId), 1, eventSize, time, 1f)
|
||||||
|
)
|
||||||
|
}),
|
||||||
eventSize,
|
eventSize,
|
||||||
eventSize,
|
eventSize,
|
||||||
eventSize,
|
eventSize,
|
||||||
|
|
@ -700,10 +755,11 @@ object KillContributions {
|
||||||
faction: PlanetSideEmpire.Value,
|
faction: PlanetSideEmpire.Value,
|
||||||
time: Long,
|
time: Long,
|
||||||
contributions: Map[SourceUniqueness, List[InGameActivity]],
|
contributions: Map[SourceUniqueness, List[InGameActivity]],
|
||||||
excludedTargets: mutable.ListBuffer[SourceUniqueness]
|
excludedTargets: mutable.ListBuffer[SourceUniqueness],
|
||||||
|
eventOutputType: String
|
||||||
): mutable.LongMap[ContributionStats] = {
|
): mutable.LongMap[ContributionStats] = {
|
||||||
val start: Long = time - 600000L
|
val start: Long = time - Config.app.game.experience.longContributionTime
|
||||||
extractContributionsForMachineByTarget(target, faction, time, start, contributions, excludedTargets)
|
extractContributionsForMachineByTarget(target, faction, time, start, contributions, excludedTargets, eventOutputType)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -726,11 +782,12 @@ object KillContributions {
|
||||||
eventTime: Long,
|
eventTime: Long,
|
||||||
startTime: Long,
|
startTime: Long,
|
||||||
contributions: Map[SourceUniqueness, List[InGameActivity]],
|
contributions: Map[SourceUniqueness, List[InGameActivity]],
|
||||||
excludedTargets: mutable.ListBuffer[SourceUniqueness]
|
excludedTargets: mutable.ListBuffer[SourceUniqueness],
|
||||||
|
eventOutputType: String
|
||||||
): mutable.LongMap[ContributionStats] = {
|
): mutable.LongMap[ContributionStats] = {
|
||||||
val unique = target.unique
|
val unique = target.unique
|
||||||
val history = limitHistoryToThisLife(contributions.getOrElse(unique, List()), eventTime, startTime)
|
val history = limitHistoryToThisLife(contributions.getOrElse(unique, List()), eventTime, startTime)
|
||||||
extractContributionsForMachineByTarget(target, faction, history, contributions, excludedTargets)
|
extractContributionsForMachineByTarget(target, faction, history, contributions, excludedTargets, eventOutputType)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -753,12 +810,13 @@ object KillContributions {
|
||||||
faction: PlanetSideEmpire.Value,
|
faction: PlanetSideEmpire.Value,
|
||||||
history: List[InGameActivity],
|
history: List[InGameActivity],
|
||||||
contributions: Map[SourceUniqueness, List[InGameActivity]],
|
contributions: Map[SourceUniqueness, List[InGameActivity]],
|
||||||
excludedTargets: mutable.ListBuffer[SourceUniqueness]
|
excludedTargets: mutable.ListBuffer[SourceUniqueness],
|
||||||
|
eventOutputType: String
|
||||||
): mutable.LongMap[ContributionStats] = {
|
): mutable.LongMap[ContributionStats] = {
|
||||||
val unique = target.unique
|
val unique = target.unique
|
||||||
if (!excludedTargets.contains(unique) && history.nonEmpty) {
|
if (!excludedTargets.contains(unique) && history.nonEmpty) {
|
||||||
excludedTargets.addOne(unique)
|
excludedTargets.addOne(unique)
|
||||||
val process = new MachineRecoveryExperienceContributionProcess(faction, contributions, excludedTargets)
|
val process = new MachineRecoveryExperienceContributionProcess(faction, contributions, eventOutputType, excludedTargets)
|
||||||
process.submit(history)
|
process.submit(history)
|
||||||
cullContributorImplements(process.output())
|
cullContributorImplements(process.output())
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ package net.psforever.objects.zones.exp
|
||||||
import net.psforever.objects.sourcing.PlayerSource
|
import net.psforever.objects.sourcing.PlayerSource
|
||||||
import net.psforever.objects.vital.{InGameActivity, ReconstructionActivity, RepairFromExoSuitChange, SpawningActivity}
|
import net.psforever.objects.vital.{InGameActivity, ReconstructionActivity, RepairFromExoSuitChange, SpawningActivity}
|
||||||
import net.psforever.types.{ExoSuitType, PlanetSideEmpire}
|
import net.psforever.types.{ExoSuitType, PlanetSideEmpire}
|
||||||
|
import net.psforever.util.Config
|
||||||
|
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
|
|
||||||
|
|
@ -11,6 +12,8 @@ import scala.collection.mutable
|
||||||
* Functions to assist experience calculation and history manipulation and analysis.
|
* Functions to assist experience calculation and history manipulation and analysis.
|
||||||
*/
|
*/
|
||||||
object Support {
|
object Support {
|
||||||
|
private val sep = Config.app.game.experience.sep
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate a base experience value to consider additional reasons for points.
|
* Calculate a base experience value to consider additional reasons for points.
|
||||||
* @param victim player to which a final interaction has reduced health to zero
|
* @param victim player to which a final interaction has reduced health to zero
|
||||||
|
|
@ -27,18 +30,19 @@ object Support {
|
||||||
case _ => 0L
|
case _ => 0L
|
||||||
}
|
}
|
||||||
val base = if (Support.wasEverAMax(victim, history)) {
|
val base = if (Support.wasEverAMax(victim, history)) {
|
||||||
250L
|
Config.app.game.experience.bep.base.asMax
|
||||||
} else if (victim.Seated || victim.progress.kills.nonEmpty) {
|
} else if (victim.progress.kills.nonEmpty) {
|
||||||
100L
|
Config.app.game.experience.bep.base.withKills
|
||||||
|
} else if (victim.Seated) {
|
||||||
|
Config.app.game.experience.bep.base.asMounted
|
||||||
} else if (lifespan > 15000L) {
|
} else if (lifespan > 15000L) {
|
||||||
50L
|
Config.app.game.experience.bep.base.mature
|
||||||
} else {
|
} else {
|
||||||
1L
|
1L
|
||||||
}
|
}
|
||||||
if (base > 1) {
|
if (base > 1) {
|
||||||
//black ops modifier
|
//black ops modifier
|
||||||
//TODO x10
|
base * Config.app.game.experience.bep.base.bopsMultiplier
|
||||||
base
|
|
||||||
} else {
|
} else {
|
||||||
base
|
base
|
||||||
}
|
}
|
||||||
|
|
@ -187,4 +191,49 @@ object Support {
|
||||||
case _ => false
|
case _ => false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take a weapon statistics entry and calculate the support experience value resulting from this support event.
|
||||||
|
* The complete formula is:<br><br>
|
||||||
|
* `base + shots-multplier * ln(shots^exp + 2) + amount-multiplier * amount`<br><br>
|
||||||
|
* ... where the middle field can be truncated into:<br><br>
|
||||||
|
* `shots-multplier * shots`<br><br>
|
||||||
|
* ... without the natural logarithm exponent defined.
|
||||||
|
* Limits can be applied to the number of shots and/or to the amount,
|
||||||
|
* which will either zero the calculations or cap the results.
|
||||||
|
* @param event identification for the event calculation parameters
|
||||||
|
* @param weaponStat base weapon stat entry to be modified
|
||||||
|
* @param canNotFindEventDefaultValue custom default value
|
||||||
|
* @return weapon stat entry with a modified for the experience
|
||||||
|
*/
|
||||||
|
private[exp] def calculateSupportExperience(
|
||||||
|
event: String,
|
||||||
|
weaponStat: WeaponStats,
|
||||||
|
canNotFindEventDefaultValue: Option[Float] = None
|
||||||
|
): WeaponStats = {
|
||||||
|
val rewards: Float = sep.events
|
||||||
|
.find(evt => event.equals(evt.name))
|
||||||
|
.map { event =>
|
||||||
|
val shots = weaponStat.shots
|
||||||
|
val shotsMultiplier = event.shotsMultiplier
|
||||||
|
if (shotsMultiplier > 0f && shots < event.shotsCutoff) {
|
||||||
|
val modifiedShotsReward: Float = {
|
||||||
|
val partialShots = math.min(event.shotsLimit, shots).toFloat
|
||||||
|
shotsMultiplier * (if (event.shotsNatLog > 0f) {
|
||||||
|
math.log(math.pow(partialShots, event.shotsNatLog) + 2d).toFloat
|
||||||
|
} else {
|
||||||
|
partialShots
|
||||||
|
})
|
||||||
|
}
|
||||||
|
val modifiedAmountReward: Float = event.amountMultiplier * weaponStat.amount.toFloat
|
||||||
|
event.base + modifiedShotsReward + modifiedAmountReward
|
||||||
|
} else {
|
||||||
|
0f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.getOrElse(
|
||||||
|
canNotFindEventDefaultValue.getOrElse(sep.canNotFindEventDefaultValue.toFloat)
|
||||||
|
)
|
||||||
|
weaponStat.copy(contributions = rewards)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
package net.psforever.objects.zones.exp
|
package net.psforever.objects.zones.exp
|
||||||
|
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
|
|
||||||
import net.psforever.objects.avatar.scoring.EquipmentStat
|
import net.psforever.objects.avatar.scoring.EquipmentStat
|
||||||
import net.psforever.objects.serverobject.hackable.Hackable.HackInfo
|
import net.psforever.objects.serverobject.hackable.Hackable.HackInfo
|
||||||
import net.psforever.objects.sourcing.VehicleSource
|
import net.psforever.objects.sourcing.VehicleSource
|
||||||
|
|
@ -11,8 +10,6 @@ import net.psforever.types.Vector3
|
||||||
import net.psforever.util.Database.ctx
|
import net.psforever.util.Database.ctx
|
||||||
import net.psforever.util.Database.ctx._
|
import net.psforever.util.Database.ctx._
|
||||||
|
|
||||||
import scala.util.Success
|
|
||||||
|
|
||||||
object ToDatabase {
|
object ToDatabase {
|
||||||
/**
|
/**
|
||||||
* Insert an entry into the database's `killactivity` table.
|
* Insert an entry into the database's `killactivity` table.
|
||||||
|
|
@ -57,7 +54,7 @@ object ToDatabase {
|
||||||
position: Vector3,
|
position: Vector3,
|
||||||
exp: Long
|
exp: Long
|
||||||
): Unit = {
|
): Unit = {
|
||||||
ctx.run(query[persistence.Assistactivity]
|
ctx.run(query[persistence.Killactivity]
|
||||||
.insert(
|
.insert(
|
||||||
_.killerId -> lift(avatarId),
|
_.killerId -> lift(avatarId),
|
||||||
_.victimId -> lift(victimId),
|
_.victimId -> lift(victimId),
|
||||||
|
|
@ -115,6 +112,10 @@ object ToDatabase {
|
||||||
_.assists -> lift(0),
|
_.assists -> lift(0),
|
||||||
_.sessionId -> lift(-1L)
|
_.sessionId -> lift(-1L)
|
||||||
)
|
)
|
||||||
|
.onConflictUpdate(_.avatarId, _.weaponId, _.sessionId)(
|
||||||
|
(t, e) => t.shotsFired -> (t.shotsFired + e.shotsFired),
|
||||||
|
(t, e) => t.shotsLanded -> (t.shotsLanded + e.shotsLanded)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -151,4 +152,66 @@ object ToDatabase {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert an entry into the database's `ntuactivity` table.
|
||||||
|
* This table monitors experience earned through NTU silo operations and
|
||||||
|
* first time event entity interactions (zone and building set to 0).
|
||||||
|
*/
|
||||||
|
def reportNtuActivity(
|
||||||
|
avatarId: Long,
|
||||||
|
zoneId: Int,
|
||||||
|
buildingId: Int,
|
||||||
|
experience: Long
|
||||||
|
): Unit = {
|
||||||
|
ctx.run(query[persistence.Ntuactivity]
|
||||||
|
.insert(
|
||||||
|
_.avatarId -> lift(avatarId),
|
||||||
|
_.zoneId -> lift(zoneId),
|
||||||
|
_.buildingId -> lift(buildingId),
|
||||||
|
_.exp -> lift(experience)
|
||||||
|
)
|
||||||
|
.onConflictUpdate(_.avatarId, _.zoneId, _.buildingId)(
|
||||||
|
(t, e) => t.exp -> (t.exp + e.exp)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert an entry into the database's `kdasession` table
|
||||||
|
* to specifically update the revive counter for the current session.
|
||||||
|
*/
|
||||||
|
def reportRespawns(
|
||||||
|
avatarId: Long,
|
||||||
|
reviveCount: Int
|
||||||
|
): Unit = {
|
||||||
|
ctx.run(query[persistence.Kdasession]
|
||||||
|
.insert(
|
||||||
|
_.avatarId -> lift(avatarId),
|
||||||
|
_.revives -> lift(reviveCount),
|
||||||
|
_.sessionId -> lift(-1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert an entry into the database's `buildingCapture` table.
|
||||||
|
*/
|
||||||
|
def reportFacilityCapture(
|
||||||
|
avatarId: Long,
|
||||||
|
zoneId: Int,
|
||||||
|
buildingId: Int,
|
||||||
|
exp: Long,
|
||||||
|
expType: String
|
||||||
|
): Unit = {
|
||||||
|
ctx.run(query[persistence.Buildingcapture]
|
||||||
|
.insert(
|
||||||
|
_.avatarId -> lift(avatarId),
|
||||||
|
_.zoneId -> lift(zoneId),
|
||||||
|
_.buildingId -> lift(buildingId),
|
||||||
|
_.exp -> lift(exp),
|
||||||
|
_.expType -> lift(expType)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package net.psforever.objects.zones.exp.rec
|
||||||
|
|
||||||
import net.psforever.objects.sourcing.SourceUniqueness
|
import net.psforever.objects.sourcing.SourceUniqueness
|
||||||
import net.psforever.objects.vital.InGameActivity
|
import net.psforever.objects.vital.InGameActivity
|
||||||
import net.psforever.objects.zones.exp.{ContributionStats, KillContributions, WeaponStats}
|
import net.psforever.objects.zones.exp.{ContributionStats, KillContributions, Support, WeaponStats}
|
||||||
import net.psforever.types.PlanetSideEmpire
|
import net.psforever.types.PlanetSideEmpire
|
||||||
|
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
|
|
@ -58,23 +58,32 @@ class CombinedHealthAndArmorContributionProcess(
|
||||||
}
|
}
|
||||||
.map {
|
.map {
|
||||||
case (id, entry) =>
|
case (id, entry) =>
|
||||||
|
var totalShots: Int = 0
|
||||||
|
var totalAmount: Int = 0
|
||||||
|
var mostRecentTime: Long = 0
|
||||||
val groupedWeapons = entry.weapons
|
val groupedWeapons = entry.weapons
|
||||||
.groupBy(_.equipment)
|
.groupBy(_.equipment)
|
||||||
.map {
|
.map {
|
||||||
case (weaponId, weaponEntries) =>
|
case (weaponContext, weaponEntries) =>
|
||||||
val specificEntries = weaponEntries.filter(_.equipment == weaponId)
|
val specificEntries = weaponEntries.filter(_.equipment == weaponContext)
|
||||||
val amount = specificEntries.foldLeft(0)(_ + _.amount)
|
val amount = specificEntries.foldLeft(0)(_ + _.amount)
|
||||||
|
totalAmount = totalAmount + amount
|
||||||
val shots = specificEntries.foldLeft(0)(_ + _.shots)
|
val shots = specificEntries.foldLeft(0)(_ + _.shots)
|
||||||
WeaponStats(weaponId, amount, shots, specificEntries.maxBy(_.time).time, 1f)
|
totalShots = totalShots + shots
|
||||||
|
val time = specificEntries.maxBy(_.time).time
|
||||||
|
mostRecentTime = math.max(mostRecentTime, time)
|
||||||
|
Support.calculateSupportExperience(
|
||||||
|
event = "support-heal",
|
||||||
|
WeaponStats(weaponContext, amount, shots, time, 1f)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
.toSeq
|
.toSeq
|
||||||
(id, ContributionStats(
|
(id, entry.copy(
|
||||||
player = entry.player,
|
|
||||||
weapons = groupedWeapons,
|
weapons = groupedWeapons,
|
||||||
amount = entry.amount + entry.amount,
|
amount = totalAmount,
|
||||||
total = entry.total + entry.total,
|
total = math.max(entry.total, totalAmount),
|
||||||
shots = groupedWeapons.foldLeft(0)(_ + _.shots),
|
shots = totalShots,
|
||||||
time = groupedWeapons.maxBy(_.time).time
|
time = mostRecentTime
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,15 @@ package net.psforever.objects.zones.exp.rec
|
||||||
|
|
||||||
import net.psforever.objects.sourcing.SourceUniqueness
|
import net.psforever.objects.sourcing.SourceUniqueness
|
||||||
import net.psforever.objects.vital.{DamagingActivity, InGameActivity, RepairFromEquipment, RepairingActivity}
|
import net.psforever.objects.vital.{DamagingActivity, InGameActivity, RepairFromEquipment, RepairingActivity}
|
||||||
import net.psforever.objects.zones.exp.ContributionStats
|
import net.psforever.objects.zones.exp.{ContributionStats, Support, WeaponStats}
|
||||||
import net.psforever.types.PlanetSideEmpire
|
import net.psforever.types.PlanetSideEmpire
|
||||||
|
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
|
|
||||||
//noinspection ScalaUnusedSymbol
|
|
||||||
class MachineRecoveryExperienceContributionProcess(
|
class MachineRecoveryExperienceContributionProcess(
|
||||||
private val faction : PlanetSideEmpire.Value,
|
private val faction : PlanetSideEmpire.Value,
|
||||||
private val contributions: Map[SourceUniqueness, List[InGameActivity]],
|
private val contributions: Map[SourceUniqueness, List[InGameActivity]],
|
||||||
|
eventOutputType: String,
|
||||||
private val excludedTargets: mutable.ListBuffer[SourceUniqueness] = mutable.ListBuffer()
|
private val excludedTargets: mutable.ListBuffer[SourceUniqueness] = mutable.ListBuffer()
|
||||||
) extends RecoveryExperienceContributionProcess(faction, contributions) {
|
) extends RecoveryExperienceContributionProcess(faction, contributions) {
|
||||||
def submit(history: List[InGameActivity]): Unit = {
|
def submit(history: List[InGameActivity]): Unit = {
|
||||||
|
|
@ -65,11 +65,9 @@ class MachineRecoveryExperienceContributionProcess(
|
||||||
.map { case (wrapper, entries) =>
|
.map { case (wrapper, entries) =>
|
||||||
val size = entries.size
|
val size = entries.size
|
||||||
val newTime = entries.maxBy(_.time).time
|
val newTime = entries.maxBy(_.time).time
|
||||||
entries.head.copy(
|
Support.calculateSupportExperience(
|
||||||
shots = size,
|
eventOutputType,
|
||||||
amount = entries.foldLeft(0)(_ + _.amount),
|
WeaponStats(wrapper, size, entries.foldLeft(0)(_ + _.amount), newTime, 1f)
|
||||||
contributions = (10 + size).toFloat,
|
|
||||||
time = newTime
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.toSeq
|
.toSeq
|
||||||
|
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
package net.psforever.persistence
|
|
||||||
|
|
||||||
import org.joda.time.LocalDateTime
|
|
||||||
|
|
||||||
case class Assistactivity(
|
|
||||||
index: Int,
|
|
||||||
killerId: Long,
|
|
||||||
victimId: Long,
|
|
||||||
weaponId: Int,
|
|
||||||
zoneId: Int,
|
|
||||||
px: Int, //Position.x * 1000
|
|
||||||
py: Int, //Position.y * 1000
|
|
||||||
pz: Int, //Position.z * 1000
|
|
||||||
exp: Long,
|
|
||||||
timestamp: LocalDateTime = LocalDateTime.now()
|
|
||||||
)
|
|
||||||
95
src/main/scala/net/psforever/persistence/KdaExp.scala
Normal file
95
src/main/scala/net/psforever/persistence/KdaExp.scala
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
// Copyright (c) 2023 PSForever
|
||||||
|
package net.psforever.persistence
|
||||||
|
|
||||||
|
import org.joda.time.LocalDateTime
|
||||||
|
|
||||||
|
case class Assistactivity(
|
||||||
|
index: Int,
|
||||||
|
killerId: Long,
|
||||||
|
victimId: Long,
|
||||||
|
weaponId: Int,
|
||||||
|
zoneId: Int,
|
||||||
|
px: Int, //Position.x * 1000
|
||||||
|
py: Int, //Position.y * 1000
|
||||||
|
pz: Int, //Position.z * 1000
|
||||||
|
exp: Long,
|
||||||
|
timestamp: LocalDateTime = LocalDateTime.now()
|
||||||
|
)
|
||||||
|
|
||||||
|
case class Buildingcapture(
|
||||||
|
index: Int,
|
||||||
|
avatarId: Long,
|
||||||
|
zoneId: Int,
|
||||||
|
buildingId: Int,
|
||||||
|
exp: Long,
|
||||||
|
expType: String,
|
||||||
|
timestamp: LocalDateTime = LocalDateTime.now()
|
||||||
|
)
|
||||||
|
|
||||||
|
case class Kdasession (
|
||||||
|
avatarId: Long,
|
||||||
|
sessionId: Int,
|
||||||
|
kills: Int,
|
||||||
|
deaths: Int,
|
||||||
|
assists: Int,
|
||||||
|
revives: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
case class Killactivity(
|
||||||
|
index: Int,
|
||||||
|
killerId: Long,
|
||||||
|
victimId: Long,
|
||||||
|
victimExosuit: Int,
|
||||||
|
victimMounted: Int, //object type id * 10 + seat type
|
||||||
|
weaponId: Int,
|
||||||
|
zoneId: Int,
|
||||||
|
px: Int, //Position.x * 1000
|
||||||
|
py: Int, //Position.y * 1000
|
||||||
|
pz: Int, //Position.z * 1000
|
||||||
|
exp: Long,
|
||||||
|
timestamp: LocalDateTime = LocalDateTime.now()
|
||||||
|
)
|
||||||
|
|
||||||
|
case class Machinedestroyed(
|
||||||
|
index: Int,
|
||||||
|
avatarId: Long,
|
||||||
|
weaponId: Int,
|
||||||
|
machineType: Int,
|
||||||
|
machineFaction: Int,
|
||||||
|
hackedFaction: Int,
|
||||||
|
asCargo: Boolean,
|
||||||
|
zoneNum: Int,
|
||||||
|
px: Int, //Position.x * 1000
|
||||||
|
py: Int, //Position.y * 1000
|
||||||
|
pz: Int, //Position.z * 1000
|
||||||
|
timestamp: LocalDateTime = LocalDateTime.now()
|
||||||
|
)
|
||||||
|
|
||||||
|
case class Ntuactivity (
|
||||||
|
avatarId: Long,
|
||||||
|
zoneId: Int,
|
||||||
|
buildingId: Int,
|
||||||
|
exp: Long
|
||||||
|
)
|
||||||
|
|
||||||
|
case class Supportactivity(
|
||||||
|
index: Int,
|
||||||
|
userId: Long,
|
||||||
|
targetId: Long,
|
||||||
|
targetExosuit: Int,
|
||||||
|
interactionType: Int,
|
||||||
|
implementType: Int,
|
||||||
|
intermediateType: Int,
|
||||||
|
exp: Long,
|
||||||
|
timestamp: LocalDateTime = LocalDateTime.now()
|
||||||
|
)
|
||||||
|
|
||||||
|
case class Weaponstatsession(
|
||||||
|
avatarId: Long,
|
||||||
|
weaponId: Int,
|
||||||
|
shotsFired: Int,
|
||||||
|
shotsLanded: Int,
|
||||||
|
kills: Int,
|
||||||
|
assists: Int,
|
||||||
|
sessionId: Long
|
||||||
|
)
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
// Copyright (c) 2022 PSForever
|
|
||||||
package net.psforever.persistence
|
|
||||||
|
|
||||||
import org.joda.time.LocalDateTime
|
|
||||||
|
|
||||||
case class Killactivity(
|
|
||||||
index: Int,
|
|
||||||
killerId: Long,
|
|
||||||
victimId: Long,
|
|
||||||
victimExosuit: Int,
|
|
||||||
victimMounted: Int, //object type id * 10 + seat type
|
|
||||||
weaponId: Int,
|
|
||||||
zoneId: Int,
|
|
||||||
px: Int, //Position.x * 1000
|
|
||||||
py: Int, //Position.y * 1000
|
|
||||||
pz: Int, //Position.z * 1000
|
|
||||||
exp: Long,
|
|
||||||
timestamp: LocalDateTime = LocalDateTime.now()
|
|
||||||
)
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
// Copyright (c) 2022 PSForever
|
|
||||||
package net.psforever.persistence
|
|
||||||
|
|
||||||
import org.joda.time.LocalDateTime
|
|
||||||
|
|
||||||
case class Machinedestroyed(
|
|
||||||
index: Int,
|
|
||||||
avatarId: Long,
|
|
||||||
weaponId: Int,
|
|
||||||
machineType: Int,
|
|
||||||
machineFaction: Int,
|
|
||||||
hackedFaction: Int,
|
|
||||||
asCargo: Boolean,
|
|
||||||
zoneNum: Int,
|
|
||||||
px: Int, //Position.x * 1000
|
|
||||||
py: Int, //Position.y * 1000
|
|
||||||
pz: Int, //Position.z * 1000
|
|
||||||
timestamp: LocalDateTime = LocalDateTime.now()
|
|
||||||
)
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
// Copyright (c) 2022 PSForever
|
|
||||||
package net.psforever.persistence
|
|
||||||
|
|
||||||
import org.joda.time.LocalDateTime
|
|
||||||
|
|
||||||
case class Supportactivity(
|
|
||||||
index: Int,
|
|
||||||
userId: Long,
|
|
||||||
targetId: Long,
|
|
||||||
targetExosuit: Int,
|
|
||||||
interactionType: Int,
|
|
||||||
implementType: Int,
|
|
||||||
intermediateType: Int,
|
|
||||||
exp: Long,
|
|
||||||
timestamp: LocalDateTime = LocalDateTime.now()
|
|
||||||
)
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
// Copyright (c) 2022 PSForever
|
|
||||||
package net.psforever.persistence
|
|
||||||
|
|
||||||
case class Weaponstatsession(
|
|
||||||
avatarId: Long,
|
|
||||||
weaponId: Int,
|
|
||||||
shotsFired: Int,
|
|
||||||
shotsLanded: Int,
|
|
||||||
kills: Int,
|
|
||||||
assists: Int,
|
|
||||||
sessionId: Long
|
|
||||||
)
|
|
||||||
|
|
@ -82,7 +82,7 @@ abstract class RemoverActor() extends SupportActor[RemoverActor.Entry] {
|
||||||
entryManagementBehaviors
|
entryManagementBehaviors
|
||||||
.orElse {
|
.orElse {
|
||||||
case RemoverActor.AddTask(obj, zone, duration) =>
|
case RemoverActor.AddTask(obj, zone, duration) =>
|
||||||
val entry = RemoverActor.Entry(obj, zone, duration.getOrElse(FirstStandardDuration).toNanos)
|
val entry = RemoverActor.Entry(obj, zone, duration.getOrElse(FirstStandardDuration).toMillis)
|
||||||
if (InclusionTest(entry) && !secondHeap.exists(test => sameEntryComparator.Test(test, entry))) {
|
if (InclusionTest(entry) && !secondHeap.exists(test => sameEntryComparator.Test(test, entry))) {
|
||||||
InitialJob(entry)
|
InitialJob(entry)
|
||||||
if (entry.duration == 0) {
|
if (entry.duration == 0) {
|
||||||
|
|
@ -120,7 +120,7 @@ abstract class RemoverActor() extends SupportActor[RemoverActor.Entry] {
|
||||||
case RemoverActor.StartDelete() =>
|
case RemoverActor.StartDelete() =>
|
||||||
firstTask.cancel()
|
firstTask.cancel()
|
||||||
secondTask.cancel()
|
secondTask.cancel()
|
||||||
val now: Long = System.nanoTime
|
val now: Long = System.currentTimeMillis()
|
||||||
val (in, out) = firstHeap.partition(entry => {
|
val (in, out) = firstHeap.partition(entry => {
|
||||||
now - entry.time >= entry.duration
|
now - entry.time >= entry.duration
|
||||||
})
|
})
|
||||||
|
|
@ -229,19 +229,19 @@ abstract class RemoverActor() extends SupportActor[RemoverActor.Entry] {
|
||||||
* this new entry is always set to last for the duration of the second pool
|
* this new entry is always set to last for the duration of the second pool
|
||||||
*/
|
*/
|
||||||
private def RepackageEntry(entry: RemoverActor.Entry): RemoverActor.Entry = {
|
private def RepackageEntry(entry: RemoverActor.Entry): RemoverActor.Entry = {
|
||||||
RemoverActor.Entry(entry.obj, entry.zone, SecondStandardDuration.toNanos)
|
RemoverActor.Entry(entry.obj, entry.zone, SecondStandardDuration.toMillis)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Common function to reset the first task's delayed execution.
|
* Common function to reset the first task's delayed execution.
|
||||||
* Cancels the scheduled timer and will only restart the timer if there is at least one entry in the first pool.
|
* Cancels the scheduled timer and will only restart the timer if there is at least one entry in the first pool.
|
||||||
* @param now the time (in nanoseconds);
|
* @param now the time (in milliseconds);
|
||||||
* defaults to the current time (in nanoseconds)
|
* defaults to the current time (in milliseconds)
|
||||||
*/
|
*/
|
||||||
def RetimeFirstTask(now: Long = System.nanoTime): Unit = {
|
def RetimeFirstTask(now: Long = System.currentTimeMillis()): Unit = {
|
||||||
firstTask.cancel()
|
firstTask.cancel()
|
||||||
if (firstHeap.nonEmpty) {
|
if (firstHeap.nonEmpty) {
|
||||||
val short_timeout: FiniteDuration = math.max(1, firstHeap.head.duration - (now - firstHeap.head.time)) nanoseconds
|
val short_timeout: FiniteDuration = math.max(1, firstHeap.head.duration - (now - firstHeap.head.time)).milliseconds
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
firstTask = context.system.scheduler.scheduleOnce(short_timeout, self, RemoverActor.StartDelete())
|
firstTask = context.system.scheduler.scheduleOnce(short_timeout, self, RemoverActor.StartDelete())
|
||||||
}
|
}
|
||||||
|
|
@ -274,14 +274,14 @@ abstract class RemoverActor() extends SupportActor[RemoverActor.Entry] {
|
||||||
/**
|
/**
|
||||||
* Default time for entries waiting in the first list.
|
* Default time for entries waiting in the first list.
|
||||||
* Override.
|
* Override.
|
||||||
* @return the time as a `FiniteDuration` object (to be later transformed into nanoseconds)
|
* @return the time as a `FiniteDuration` object (to be later transformed into milliseconds)
|
||||||
*/
|
*/
|
||||||
def FirstStandardDuration: FiniteDuration
|
def FirstStandardDuration: FiniteDuration
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default time for entries waiting in the second list.
|
* Default time for entries waiting in the second list.
|
||||||
* Override.
|
* Override.
|
||||||
* @return the time as a `FiniteDuration` object (to be later transformed into nanoseconds)
|
* @return the time as a `FiniteDuration` object (to be later transformed into milliseconds)
|
||||||
*/
|
*/
|
||||||
def SecondStandardDuration: FiniteDuration
|
def SecondStandardDuration: FiniteDuration
|
||||||
|
|
||||||
|
|
@ -322,7 +322,7 @@ object RemoverActor extends SupportActorCaseConversions {
|
||||||
* Internally, all entries have a "time created" field.
|
* Internally, all entries have a "time created" field.
|
||||||
* @param _obj the target
|
* @param _obj the target
|
||||||
* @param _zone the zone in which this target is registered
|
* @param _zone the zone in which this target is registered
|
||||||
* @param _duration how much longer the target will exist in its current state (in nanoseconds)
|
* @param _duration how much longer the target will exist in its current state (in milliseconds)
|
||||||
*/
|
*/
|
||||||
case class Entry(_obj: PlanetSideGameObject, _zone: Zone, _duration: Long)
|
case class Entry(_obj: PlanetSideGameObject, _zone: Zone, _duration: Long)
|
||||||
extends SupportActor.Entry(_obj, _zone, _duration)
|
extends SupportActor.Entry(_obj, _zone, _duration)
|
||||||
|
|
@ -332,7 +332,7 @@ object RemoverActor extends SupportActorCaseConversions {
|
||||||
* @see `FirstStandardDuration`
|
* @see `FirstStandardDuration`
|
||||||
* @param obj the target
|
* @param obj the target
|
||||||
* @param zone the zone in which this target is registered
|
* @param zone the zone in which this target is registered
|
||||||
* @param duration how much longer the target will exist in its current state (in nanoseconds);
|
* @param duration how much longer the target will exist in its current state (in milliseconds);
|
||||||
* a default time duration is provided by implementation
|
* a default time duration is provided by implementation
|
||||||
*/
|
*/
|
||||||
case class AddTask(obj: PlanetSideGameObject, zone: Zone, duration: Option[FiniteDuration] = None)
|
case class AddTask(obj: PlanetSideGameObject, zone: Zone, duration: Option[FiniteDuration] = None)
|
||||||
|
|
|
||||||
|
|
@ -456,6 +456,15 @@ class AvatarService(zone: Zone) extends Actor {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
case AvatarAction.FacilityCaptureRewards(building_id, zone_number, exp) =>
|
||||||
|
AvatarEvents.publish(
|
||||||
|
AvatarServiceResponse(
|
||||||
|
s"/$forChannel/Avatar",
|
||||||
|
Service.defaultPlayerGUID,
|
||||||
|
AvatarResponse.FacilityCaptureRewards(building_id, zone_number, exp)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
case _ => ()
|
case _ => ()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -158,6 +158,7 @@ object AvatarAction {
|
||||||
final case class UpdateKillsDeathsAssists(charId: Long, kda: KDAStat) extends Action
|
final case class UpdateKillsDeathsAssists(charId: Long, kda: KDAStat) extends Action
|
||||||
final case class AwardBep(charId: Long, bep: Long, expType: ExperienceType) extends Action
|
final case class AwardBep(charId: Long, bep: Long, expType: ExperienceType) extends Action
|
||||||
final case class AwardCep(charId: Long, bep: Long) extends Action
|
final case class AwardCep(charId: Long, bep: Long) extends Action
|
||||||
|
final case class FacilityCaptureRewards(building_id: Int, zone_number: Int, exp: Long) extends Action
|
||||||
|
|
||||||
final case class TeardownConnection() extends Action
|
final case class TeardownConnection() extends Action
|
||||||
// final case class PlayerStateShift(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action
|
// final case class PlayerStateShift(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action
|
||||||
|
|
|
||||||
|
|
@ -129,4 +129,5 @@ object AvatarResponse {
|
||||||
final case class UpdateKillsDeathsAssists(charId: Long, kda: KDAStat) extends Response
|
final case class UpdateKillsDeathsAssists(charId: Long, kda: KDAStat) extends Response
|
||||||
final case class AwardBep(charId: Long, bep: Long, expType: ExperienceType) extends Response
|
final case class AwardBep(charId: Long, bep: Long, expType: ExperienceType) extends Response
|
||||||
final case class AwardCep(charId: Long, bep: Long) extends Response
|
final case class AwardCep(charId: Long, bep: Long) extends Response
|
||||||
|
final case class FacilityCaptureRewards(building_id: Int, zone_number: Int, exp: Long) extends Response
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,8 +49,7 @@ class DoorCloseActor() extends Actor {
|
||||||
})
|
})
|
||||||
|
|
||||||
if (openDoors.nonEmpty) {
|
if (openDoors.nonEmpty) {
|
||||||
val short_timeout: FiniteDuration =
|
val short_timeout: FiniteDuration = math.max(1, DoorCloseActor.timeout_time - (now - openDoors.head.time)).milliseconds
|
||||||
math.max(1, DoorCloseActor.timeout_time - (now - openDoors.head.time)) nanoseconds
|
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
doorCloserTrigger = context.system.scheduler.scheduleOnce(short_timeout, self, DoorCloseActor.TryCloseDoors())
|
doorCloserTrigger = context.system.scheduler.scheduleOnce(short_timeout, self, DoorCloseActor.TryCloseDoors())
|
||||||
}
|
}
|
||||||
|
|
@ -67,7 +66,7 @@ class DoorCloseActor() extends Actor {
|
||||||
* and newer entries are always added to the end of the main `List`,
|
* and newer entries are always added to the end of the main `List`,
|
||||||
* processing in order is always correct.
|
* processing in order is always correct.
|
||||||
* @param list the `List` of entries to divide
|
* @param list the `List` of entries to divide
|
||||||
* @param now the time right now (in nanoseconds)
|
* @param now the time right now (in milliseconds)
|
||||||
* @see `List.partition`
|
* @see `List.partition`
|
||||||
* @return a `Tuple` of two `Lists`, whose qualifications are explained above
|
* @return a `Tuple` of two `Lists`, whose qualifications are explained above
|
||||||
*/
|
*/
|
||||||
|
|
@ -84,7 +83,7 @@ class DoorCloseActor() extends Actor {
|
||||||
* a `List` of elements that have exceeded the time limit,
|
* a `List` of elements that have exceeded the time limit,
|
||||||
* and a `List` of elements that still satisfy the time limit.
|
* and a `List` of elements that still satisfy the time limit.
|
||||||
* @param iter the `Iterator` of entries to divide
|
* @param iter the `Iterator` of entries to divide
|
||||||
* @param now the time right now (in nanoseconds)
|
* @param now the time right now (in milliseconds)
|
||||||
* @param index a persistent record of the index where list division should occur;
|
* @param index a persistent record of the index where list division should occur;
|
||||||
* defaults to 0
|
* defaults to 0
|
||||||
* @return the index where division will occur
|
* @return the index where division will occur
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ import net.psforever.services.local.support.HackCaptureActor.GetHackingFaction
|
||||||
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
||||||
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID}
|
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID}
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import scala.concurrent.duration.{FiniteDuration, _}
|
import scala.concurrent.duration.{FiniteDuration, _}
|
||||||
import scala.util.Random
|
import scala.util.Random
|
||||||
|
|
||||||
|
|
@ -38,7 +37,7 @@ class HackCaptureActor extends Actor {
|
||||||
val duration = target.Definition.FacilityHackTime
|
val duration = target.Definition.FacilityHackTime
|
||||||
target.HackedBy match {
|
target.HackedBy match {
|
||||||
case Some(hackInfo) =>
|
case Some(hackInfo) =>
|
||||||
target.HackedBy = hackInfo.Duration(duration.toNanos)
|
target.HackedBy = hackInfo.Duration(duration.toMillis)
|
||||||
case None =>
|
case None =>
|
||||||
log.error(s"Initial $target hack information is missing")
|
log.error(s"Initial $target hack information is missing")
|
||||||
}
|
}
|
||||||
|
|
@ -57,8 +56,8 @@ class HackCaptureActor extends Actor {
|
||||||
case HackCaptureActor.ProcessCompleteHacks() =>
|
case HackCaptureActor.ProcessCompleteHacks() =>
|
||||||
log.trace("Processing complete hacks")
|
log.trace("Processing complete hacks")
|
||||||
clearTrigger.cancel()
|
clearTrigger.cancel()
|
||||||
val now: Long = System.nanoTime
|
val now: Long = System.currentTimeMillis()
|
||||||
val (stillHacked, finishedHacks) = hackedObjects.partition(x => now - x.hack_timestamp < x.duration.toNanos)
|
val (stillHacked, finishedHacks) = hackedObjects.partition(x => now - x.hack_timestamp < x.duration.toMillis)
|
||||||
hackedObjects = stillHacked
|
hackedObjects = stillHacked
|
||||||
finishedHacks.foreach { entry =>
|
finishedHacks.foreach { entry =>
|
||||||
val terminal = entry.target
|
val terminal = entry.target
|
||||||
|
|
@ -213,9 +212,8 @@ class HackCaptureActor extends Actor {
|
||||||
|
|
||||||
private def RestartTimer(): Unit = {
|
private def RestartTimer(): Unit = {
|
||||||
if (hackedObjects.nonEmpty) {
|
if (hackedObjects.nonEmpty) {
|
||||||
val hackEntry = hackedObjects.reduceLeft(HackCaptureActor.minTimeLeft(System.nanoTime()))
|
val hackEntry = hackedObjects.reduceLeft(HackCaptureActor.minTimeLeft(System.currentTimeMillis()))
|
||||||
val short_timeout: FiniteDuration =
|
val short_timeout: FiniteDuration = math.max(1, hackEntry.duration.toMillis - (System.currentTimeMillis() - hackEntry.hack_timestamp)).milliseconds
|
||||||
math.max(1, hackEntry.duration.toNanos - (System.nanoTime - hackEntry.hack_timestamp)).nanoseconds
|
|
||||||
log.trace(s"RestartTimer: still items left in hacked objects list. Checking again in ${short_timeout.toSeconds} seconds")
|
log.trace(s"RestartTimer: still items left in hacked objects list. Checking again in ${short_timeout.toSeconds} seconds")
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
clearTrigger = context.system.scheduler.scheduleOnce(short_timeout, self, HackCaptureActor.ProcessCompleteHacks())
|
clearTrigger = context.system.scheduler.scheduleOnce(short_timeout, self, HackCaptureActor.ProcessCompleteHacks())
|
||||||
|
|
@ -229,7 +227,7 @@ object HackCaptureActor {
|
||||||
zone: Zone,
|
zone: Zone,
|
||||||
unk1: Long,
|
unk1: Long,
|
||||||
unk2: Long,
|
unk2: Long,
|
||||||
startTime: Long = System.nanoTime()
|
startTime: Long = System.currentTimeMillis()
|
||||||
)
|
)
|
||||||
|
|
||||||
final case class ResecureCaptureTerminal(target: CaptureTerminal, zone: Zone, hacker: PlayerSource)
|
final case class ResecureCaptureTerminal(target: CaptureTerminal, zone: Zone, hacker: PlayerSource)
|
||||||
|
|
@ -256,8 +254,7 @@ object HackCaptureActor {
|
||||||
17039360L
|
17039360L
|
||||||
case Some(Hackable.HackInfo(p, _, start, length)) =>
|
case Some(Hackable.HackInfo(p, _, start, length)) =>
|
||||||
// See PlanetSideAttributeMessage #20 documentation for an explanation of how the timer is calculated
|
// See PlanetSideAttributeMessage #20 documentation for an explanation of how the timer is calculated
|
||||||
val hackTimeRemainingMS =
|
val hackTimeRemainingMS = math.max(0, start + length - System.currentTimeMillis())
|
||||||
TimeUnit.MILLISECONDS.convert(math.max(0, start + length - System.nanoTime), TimeUnit.NANOSECONDS)
|
|
||||||
val startNum = p.Faction match {
|
val startNum = p.Faction match {
|
||||||
case PlanetSideEmpire.TR => 0x10000
|
case PlanetSideEmpire.TR => 0x10000
|
||||||
case PlanetSideEmpire.NC => 0x20000
|
case PlanetSideEmpire.NC => 0x20000
|
||||||
|
|
@ -273,8 +270,8 @@ object HackCaptureActor {
|
||||||
entry1: HackCaptureActor.HackEntry,
|
entry1: HackCaptureActor.HackEntry,
|
||||||
entry2: HackCaptureActor.HackEntry
|
entry2: HackCaptureActor.HackEntry
|
||||||
): HackCaptureActor.HackEntry = {
|
): HackCaptureActor.HackEntry = {
|
||||||
val entry1TimeLeft = entry1.duration.toNanos - (now - entry1.hack_timestamp)
|
val entry1TimeLeft = entry1.duration.toMillis - (now - entry1.hack_timestamp)
|
||||||
val entry2TimeLeft = entry2.duration.toNanos - (now - entry2.hack_timestamp)
|
val entry2TimeLeft = entry2.duration.toMillis - (now - entry2.hack_timestamp)
|
||||||
if (entry1TimeLeft < entry2TimeLeft) {
|
if (entry1TimeLeft < entry2TimeLeft) {
|
||||||
entry1
|
entry1
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -29,15 +29,15 @@ class HackClearActor() extends Actor {
|
||||||
|
|
||||||
def receive: Receive = {
|
def receive: Receive = {
|
||||||
case HackClearActor.ObjectIsHacked(target, zone, unk1, unk2, duration, time) =>
|
case HackClearActor.ObjectIsHacked(target, zone, unk1, unk2, duration, time) =>
|
||||||
val durationNanos = TimeUnit.NANOSECONDS.convert(duration, TimeUnit.SECONDS)
|
val durationMillis = TimeUnit.MILLISECONDS.convert(duration, TimeUnit.SECONDS)
|
||||||
hackedObjects = hackedObjects :+ HackClearActor.HackEntry(target, zone, unk1, unk2, time, durationNanos)
|
hackedObjects = hackedObjects :+ HackClearActor.HackEntry(target, zone, unk1, unk2, time, durationMillis)
|
||||||
|
|
||||||
// Restart the timer, in case this is the first object in the hacked objects list
|
// Restart the timer, in case this is the first object in the hacked objects list
|
||||||
RestartTimer()
|
RestartTimer()
|
||||||
|
|
||||||
case HackClearActor.TryClearHacks() =>
|
case HackClearActor.TryClearHacks() =>
|
||||||
clearTrigger.cancel()
|
clearTrigger.cancel()
|
||||||
val now: Long = System.nanoTime
|
val now: Long = System.currentTimeMillis()
|
||||||
//TODO we can just walk across the list of doors and extract only the first few entries
|
//TODO we can just walk across the list of doors and extract only the first few entries
|
||||||
val (unhackObjects, stillHackedObjects) = PartitionEntries(hackedObjects, now)
|
val (unhackObjects, stillHackedObjects) = PartitionEntries(hackedObjects, now)
|
||||||
hackedObjects = stillHackedObjects
|
hackedObjects = stillHackedObjects
|
||||||
|
|
@ -75,12 +75,12 @@ class HackClearActor() extends Actor {
|
||||||
|
|
||||||
private def RestartTimer(): Unit = {
|
private def RestartTimer(): Unit = {
|
||||||
if (hackedObjects.nonEmpty) {
|
if (hackedObjects.nonEmpty) {
|
||||||
val now = System.nanoTime()
|
val now = System.currentTimeMillis()
|
||||||
val (_/*unhackObjects*/, stillHackedObjects) = PartitionEntries(hackedObjects, now)
|
val (_/*unhackObjects*/, stillHackedObjects) = PartitionEntries(hackedObjects, now)
|
||||||
|
|
||||||
stillHackedObjects.headOption match {
|
stillHackedObjects.headOption match {
|
||||||
case Some(hackEntry) =>
|
case Some(hackEntry) =>
|
||||||
val short_timeout: FiniteDuration = math.max(1, hackEntry.duration - (now - hackEntry.time)) nanoseconds
|
val short_timeout: FiniteDuration = math.max(1, hackEntry.duration - (now - hackEntry.time)).milliseconds
|
||||||
|
|
||||||
log.debug(
|
log.debug(
|
||||||
s"HackClearActor: still items left in hacked objects list. Checking again in ${short_timeout.toSeconds} seconds"
|
s"HackClearActor: still items left in hacked objects list. Checking again in ${short_timeout.toSeconds} seconds"
|
||||||
|
|
@ -102,7 +102,7 @@ class HackClearActor() extends Actor {
|
||||||
* and newer entries are always added to the end of the main `List`,
|
* and newer entries are always added to the end of the main `List`,
|
||||||
* processing in order is always correct.
|
* processing in order is always correct.
|
||||||
* @param list the `List` of entries to divide
|
* @param list the `List` of entries to divide
|
||||||
* @param now the time right now (in nanoseconds)
|
* @param now the time right now (in milliseconds)
|
||||||
* @see `List.partition`
|
* @see `List.partition`
|
||||||
* @return a `Tuple` of two `Lists`, whose qualifications are explained above
|
* @return a `Tuple` of two `Lists`, whose qualifications are explained above
|
||||||
*/
|
*/
|
||||||
|
|
@ -119,7 +119,7 @@ class HackClearActor() extends Actor {
|
||||||
* a `List` of elements that have exceeded the time limit,
|
* a `List` of elements that have exceeded the time limit,
|
||||||
* and a `List` of elements that still satisfy the time limit.
|
* and a `List` of elements that still satisfy the time limit.
|
||||||
* @param iter the `Iterator` of entries to divide
|
* @param iter the `Iterator` of entries to divide
|
||||||
* @param now the time right now (in nanoseconds)
|
* @param now the time right now (in milliseconds)
|
||||||
* @param index a persistent record of the index where list division should occur;
|
* @param index a persistent record of the index where list division should occur;
|
||||||
* defaults to 0
|
* defaults to 0
|
||||||
* @return the index where division will occur
|
* @return the index where division will occur
|
||||||
|
|
@ -158,7 +158,7 @@ object HackClearActor {
|
||||||
unk1: Long,
|
unk1: Long,
|
||||||
unk2: Long,
|
unk2: Long,
|
||||||
duration: Int,
|
duration: Int,
|
||||||
time: Long = System.nanoTime()
|
time: Long = System.currentTimeMillis()
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -185,7 +185,7 @@ object HackClearActor {
|
||||||
* @param target the server object
|
* @param target the server object
|
||||||
* @param zone the zone in which the object resides
|
* @param zone the zone in which the object resides
|
||||||
* @param time when the object was hacked
|
* @param time when the object was hacked
|
||||||
* @param duration The hack duration in nanoseconds
|
* @param duration The hack duration in milliseconds
|
||||||
* @see `ObjectIsHacked`
|
* @see `ObjectIsHacked`
|
||||||
*/
|
*/
|
||||||
private final case class HackEntry(
|
private final case class HackEntry(
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ abstract class SupportActor[A <: SupportActor.Entry] extends Actor {
|
||||||
|
|
||||||
object SupportActor {
|
object SupportActor {
|
||||||
class Entry(val obj: PlanetSideGameObject, val zone: Zone, val duration: Long) {
|
class Entry(val obj: PlanetSideGameObject, val zone: Zone, val duration: Long) {
|
||||||
val time: Long = System.nanoTime
|
val time: Long = System.currentTimeMillis()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -50,8 +50,8 @@ class TurretUpgrader extends SupportActor[TurretUpgrader.Entry] {
|
||||||
entryManagementBehaviors
|
entryManagementBehaviors
|
||||||
.orElse {
|
.orElse {
|
||||||
case TurretUpgrader.AddTask(turret, zone, upgrade, duration) =>
|
case TurretUpgrader.AddTask(turret, zone, upgrade, duration) =>
|
||||||
val lengthOfTime = duration.getOrElse(TurretUpgrader.StandardUpgradeLifetime).toNanos
|
val lengthOfTime = duration.getOrElse(TurretUpgrader.StandardUpgradeLifetime).toMillis
|
||||||
if (lengthOfTime > (1 second).toNanos) { //don't even bother if it's too short; it'll revert near instantly
|
if (lengthOfTime > (1 second).toMillis) { //don't even bother if it's too short; it'll revert near instantly
|
||||||
val entry = CreateEntry(turret, zone, TurretUpgrade.None, lengthOfTime)
|
val entry = CreateEntry(turret, zone, TurretUpgrade.None, lengthOfTime)
|
||||||
UpgradeTurretAmmo(CreateEntry(turret, zone, upgrade, lengthOfTime))
|
UpgradeTurretAmmo(CreateEntry(turret, zone, upgrade, lengthOfTime))
|
||||||
if (list.isEmpty) {
|
if (list.isEmpty) {
|
||||||
|
|
@ -73,7 +73,7 @@ class TurretUpgrader extends SupportActor[TurretUpgrader.Entry] {
|
||||||
|
|
||||||
case TurretUpgrader.Downgrade() =>
|
case TurretUpgrader.Downgrade() =>
|
||||||
task.cancel()
|
task.cancel()
|
||||||
val now: Long = System.nanoTime
|
val now: Long = System.currentTimeMillis()
|
||||||
val (in, out) =
|
val (in, out) =
|
||||||
list.partition(entry => {
|
list.partition(entry => {
|
||||||
now - entry.time >= entry.duration
|
now - entry.time >= entry.duration
|
||||||
|
|
@ -85,10 +85,10 @@ class TurretUpgrader extends SupportActor[TurretUpgrader.Entry] {
|
||||||
case _ => ;
|
case _ => ;
|
||||||
}
|
}
|
||||||
|
|
||||||
def RetimeFirstTask(now: Long = System.nanoTime): Unit = {
|
def RetimeFirstTask(now: Long = System.currentTimeMillis()): Unit = {
|
||||||
task.cancel()
|
task.cancel()
|
||||||
if (list.nonEmpty) {
|
if (list.nonEmpty) {
|
||||||
val short_timeout: FiniteDuration = math.max(1, list.head.duration - (now - list.head.time)) nanoseconds
|
val short_timeout: FiniteDuration = math.max(1, list.head.duration - (now - list.head.time)).milliseconds
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
task = context.system.scheduler.scheduleOnce(short_timeout, self, TurretUpgrader.Downgrade())
|
task = context.system.scheduler.scheduleOnce(short_timeout, self, TurretUpgrader.Downgrade())
|
||||||
}
|
}
|
||||||
|
|
@ -249,7 +249,7 @@ object TurretUpgrader extends SupportActorCaseConversions {
|
||||||
* @param _obj the target
|
* @param _obj the target
|
||||||
* @param _zone the zone in which this target is registered
|
* @param _zone the zone in which this target is registered
|
||||||
* @param upgrade the next upgrade state for this turret
|
* @param upgrade the next upgrade state for this turret
|
||||||
* @param _duration how much longer the target will exist in its current state (in nanoseconds)
|
* @param _duration how much longer the target will exist in its current state (in milliseconds)
|
||||||
*/
|
*/
|
||||||
case class Entry(_obj: PlanetSideGameObject, _zone: Zone, upgrade: TurretUpgrade.Value, _duration: Long)
|
case class Entry(_obj: PlanetSideGameObject, _zone: Zone, upgrade: TurretUpgrade.Value, _duration: Long)
|
||||||
extends SupportActor.Entry(_obj, _zone, _duration)
|
extends SupportActor.Entry(_obj, _zone, _duration)
|
||||||
|
|
|
||||||
|
|
@ -150,9 +150,6 @@ case class GameConfig(
|
||||||
instantAction: InstantActionConfig,
|
instantAction: InstantActionConfig,
|
||||||
amenityAutorepairRate: Float,
|
amenityAutorepairRate: Float,
|
||||||
amenityAutorepairDrainRate: Float,
|
amenityAutorepairDrainRate: Float,
|
||||||
bepRate: Double,
|
|
||||||
cepRate: Double,
|
|
||||||
maximumCepPerSquadSize: Seq[Int],
|
|
||||||
newAvatar: NewAvatar,
|
newAvatar: NewAvatar,
|
||||||
hart: HartConfig,
|
hart: HartConfig,
|
||||||
sharedMaxCooldown: Boolean,
|
sharedMaxCooldown: Boolean,
|
||||||
|
|
@ -162,7 +159,8 @@ case class GameConfig(
|
||||||
cavernRotation: CavernRotationConfig,
|
cavernRotation: CavernRotationConfig,
|
||||||
savedMsg: SavedMessageEvents,
|
savedMsg: SavedMessageEvents,
|
||||||
playerDraw: PlayerStateDrawSettings,
|
playerDraw: PlayerStateDrawSettings,
|
||||||
doorsCanBeOpenedByMedAppFromThisDistance: Float
|
doorsCanBeOpenedByMedAppFromThisDistance: Float,
|
||||||
|
experience: Experience
|
||||||
)
|
)
|
||||||
|
|
||||||
case class InstantActionConfig(
|
case class InstantActionConfig(
|
||||||
|
|
@ -238,3 +236,51 @@ case class PlayerStateDrawSettings(
|
||||||
assert(ranges.nonEmpty)
|
assert(ranges.nonEmpty)
|
||||||
assert(ranges.size == delays.size)
|
assert(ranges.size == delays.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case class Experience(
|
||||||
|
shortContributionTime: Long,
|
||||||
|
longContributionTime: Long,
|
||||||
|
bep: BattleExperiencePoints,
|
||||||
|
sep: SupportExperiencePoints,
|
||||||
|
cep: CommandExperiencePoints
|
||||||
|
) {
|
||||||
|
assert(shortContributionTime < longContributionTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
case class BattleExperiencePoints(
|
||||||
|
base: BattleExperiencePointsBase,
|
||||||
|
rate: Float
|
||||||
|
)
|
||||||
|
|
||||||
|
case class BattleExperiencePointsBase(
|
||||||
|
bopsMultiplier: Long,
|
||||||
|
asMax: Long,
|
||||||
|
withKills: Long,
|
||||||
|
asMounted: Long,
|
||||||
|
mature: Long
|
||||||
|
)
|
||||||
|
|
||||||
|
case class SupportExperiencePoints(
|
||||||
|
rate: Float,
|
||||||
|
ntuSiloDepositReward: Long,
|
||||||
|
canNotFindEventDefaultValue: Long,
|
||||||
|
events: Seq[SupportExperienceEvent]
|
||||||
|
)
|
||||||
|
|
||||||
|
case class SupportExperienceEvent(
|
||||||
|
name: String,
|
||||||
|
base: Long,
|
||||||
|
shotsMultiplier: Float = 0f,
|
||||||
|
shotsNatLog: Double = 0f,
|
||||||
|
shotsLimit: Int = 50,
|
||||||
|
shotsCutoff: Int = 50,
|
||||||
|
amountMultiplier: Float = 0f
|
||||||
|
)
|
||||||
|
|
||||||
|
case class CommandExperiencePoints(
|
||||||
|
rate: Float,
|
||||||
|
lluCarrierModifier: Float,
|
||||||
|
maximumPerSquadSize: Seq[Int],
|
||||||
|
squadSizeLimitOverflow: Int,
|
||||||
|
squadSizeLimitOverflowMultiplier: Float
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -624,7 +624,7 @@ class PlayerControlDeathStandingTest extends ActorTest {
|
||||||
// override def AvatarEvents = avatarProbe.ref
|
// override def AvatarEvents = avatarProbe.ref
|
||||||
// override def Activity = activityProbe.ref
|
// override def Activity = activityProbe.ref
|
||||||
// }
|
// }
|
||||||
// zone.actor = system.spawn(ZoneActor(zone), s"test-zone-${System.nanoTime()}")
|
// zone.actor = system.spawn(ZoneActor(zone), s"test-zone-${System.currentTimeMillis()}")
|
||||||
//
|
//
|
||||||
// player1.Zone = zone
|
// player1.Zone = zone
|
||||||
// player1.Spawn()
|
// player1.Spawn()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue