diff --git a/.vscode/build.sh b/.vscode/build.sh index 1004ea7..e3688ba 100644 --- a/.vscode/build.sh +++ b/.vscode/build.sh @@ -114,4 +114,6 @@ if [ "$needCopyOther" ]; then cp -r $srcDir/configs/* $destinationDir/configs/ print_color $GREEN " - Copying data..." cp -r $srcDir/data/* $destinationDir/data/ + print_color $GREEN " - Copying modules..." + cp -r $srcDir/modules/* $destinationDir/modules/ fi \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index dd36a2b..6dcc234 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,8 +23,8 @@ WORKDIR /root/hlds/cstrike RUN releaseLink="https://github.com/theAsmodai/metamod-r/releases/download/1.3.0.149/metamod-bin-1.3.0.149.zip" \ && curl -sSL ${releaseLink} | bsdtar -xf - --exclude='*.dll' --exclude='*.pdb' addons/* -# Install AMXModX 1.9.0 -ARG AMXModX_URL="https://www.amxmodx.org/amxxdrop/1.9/amxmodx-1.9.0-git5294-base-linux.tar.gz" +# Install AMXModX 1.10 +ARG AMXModX_URL="https://www.amxmodx.org/amxxdrop/1.10/amxmodx-1.10.0-git5467-base-linux.tar.gz" RUN curl -sSL ${AMXModX_URL} | bsdtar -xf - addons/ \ && echo "linux addons/amxmodx/dlls/amxmodx_mm_i386.so" > addons/metamod/plugins.ini diff --git a/cstrike/addons/amxmodx/modules/customentdata_amxx.dll b/cstrike/addons/amxmodx/modules/customentdata_amxx.dll new file mode 100644 index 0000000..7a4e259 Binary files /dev/null and b/cstrike/addons/amxmodx/modules/customentdata_amxx.dll differ diff --git a/cstrike/addons/amxmodx/modules/customentdata_amxx_i386.so b/cstrike/addons/amxmodx/modules/customentdata_amxx_i386.so new file mode 100644 index 0000000..f699e22 Binary files /dev/null and b/cstrike/addons/amxmodx/modules/customentdata_amxx_i386.so differ diff --git a/cstrike/addons/amxmodx/scripting/ReDeathmatch/Features/ControlPoints.inc b/cstrike/addons/amxmodx/scripting/ReDeathmatch/Features/ControlPoints.inc new file mode 100644 index 0000000..3f21ab1 --- /dev/null +++ b/cstrike/addons/amxmodx/scripting/ReDeathmatch/Features/ControlPoints.inc @@ -0,0 +1,786 @@ +// #include +// #include +// #include + +// #include + + +// Can't use `trigger` as base class cuz didn't call Spawn() and InitTrigger() +static const g_pointBaseClassname[] = "info_target" +static const g_pointClassname[] = "trigger_capture_area" + +const Float: AREA_THINK_TIME = 0.1 + +enum renderState_s { + rs_Reset, + rs_Start, + rs_inProgress, +} + +enum perTeamData_s { + iNumRequiredToCap, + iNumTouching, + iBlockedTouching, + bCanCap, + iSpawnAdjust, +} + +enum { + _pev_lastReductionTime = pev_fuser1, + _pev_timeRemaining = pev_fuser4, + _pev_captureTime = pev_fuser3, + + _pev_capturingTeam = pev_team, + _pev_isCapturing = pev_iuser2, + _pev_teamInZone = pev_iuser3, + _pev_hPoint = pev_iuser4, + _pev_owningTeam = pev_owner, + _pev_pointIdx = pev_modelindex, + _pev_bBlocked = pev_bInDuck, + _pev_bActive = pev_controller_0, + + // TODO: rework it later + _pev_teamData = pev_gamestate, // perTeamData_s[] +} + +static Float: g_sequenceRate = 0.8 +static Float: g_renderAmount = 100.0 + +static Float: g_color[TeamName][] = { + { 100.0, 100.0, 100.0 }, + { 255.0, 100.0, 0.0 }, + { 0.0, 100.0, 255.0 }, + + { 0.0, 0.0, 0.0 } // TeamName compability +} + +static const g_ControlPointsModels[TeamName][] = { + "models/player/vip/vip.mdl", + "models/player/leet/leet.mdl", + "models/player/gign/gign.mdl", + + "" // TeamName compability +} + + + + +#define MAX_CAPTURE_TEAMS TeamName + +static mp_simulatemultiplecappers = 1 +static mp_capstyle = 0 + +static const Float: SPAWN_POINT_OFFSET = 10.0 + +static Float: redm_capture_spawn_time +static redm_capture_spawn_bar +static Float: redm_capture_spawn_penalty +static Float: redm_capture_spawn_distance +static redm_capture_spawn_changelevel +static redm_capture_spawn_static +static Float: redm_capture_capdeteriorate_time + +CapturePoints_Init() { + // RegisterHookChain(RG_CBasePlayer_TakeDamage, "CBasePlayer_TakeDamage", .post = true) + // RegisterHam(Ham_StopSneaking, "info_target", "CBaseEntity_StopSneaking", .Post = true) + + RegisterHam(Ham_Touch, g_pointBaseClassname, "CTriggerAreaCapture__AreaTouch", .Post=true) + RegisterHam(Ham_Think, g_pointBaseClassname, "CTriggerAreaCapture__CaptureThink", .Post=true) + + Create_ConVars() + +#if 1 + // For testing purposes only + server_cmd("mp_auto_join_team 1; humans_join_team t; redm_randomspawn 1; mp_freeforall 0; yb_chat 0; yb_quota 2; bot_quota 0") +#endif + +#if 1 + // For testing purposes only + register_concmd("radio1", "@test") +#endif +} + +static bool: test +@test(p) { + test = !test + + return PLUGIN_HANDLED +} + +static Create_ConVars() { + bind_pcvar_float( + create_cvar( + "redm_capture_spawn_time", "10.0", + .has_min = true, .min_val = 0.0, + .flags = _FCVAR_FLOAT, + .description = "Time to capture spawn point. (0 - instantly)" + ), + redm_capture_spawn_time + ) + bind_pcvar_num( + create_cvar( + "redm_capture_spawn_bar", "1", + .has_min = true, .min_val = 0.0, + .has_max = true, .max_val = 1.0, + .flags = _FCVAR_BOOLEAN, + .description = "Point capture status bar" + ), + redm_capture_spawn_bar + ) + bind_pcvar_float( + create_cvar( + "redm_capture_spawn_penalty", "3", + .has_min = true, .min_val = 0.0, + .flags = _FCVAR_FLOAT, + .description = "The time after taking damage that the player will not be able to use points (0 - no restrictions)" + ), + redm_capture_spawn_penalty + ) + bind_pcvar_float( + create_cvar( + "redm_capture_spawn_distance", "64", + .has_min = true, .min_val = 16.0, + .has_max = true, .max_val = 256.0, + .flags = _FCVAR_FLOAT, + .description = "The interaction distance between the player and the spawn point" + ), + redm_capture_spawn_distance + ) + bind_pcvar_num( + create_cvar( + "redm_capture_spawn_changelevel", "80", + .has_min = true, .min_val = 0.0, + .has_max = true, .max_val = 100.0, + .flags = _FCVAR_INTEGER, + .description = "How many percent do you need to capture spawns to finish the game?" + ), + redm_capture_spawn_changelevel + ) + bind_pcvar_num( + create_cvar( + "redm_capture_spawn_static", "10", + .has_min = true, .min_val = 0.0, + .has_max = true, .max_val = 20.0, + .flags = _FCVAR_INTEGER, + .description = "The number of static spawns of commands that are not subject to capture" + ), + redm_capture_spawn_static + ) + bind_pcvar_float( + create_cvar( + "redm_capture_capdeteriorate_time", "15", + .has_min = true, .min_val = 0.0, + .has_max = true, .max_val = 256.0, + .flags = _FCVAR_FLOAT, + .description = "<>" + ), + redm_capture_capdeteriorate_time + ) +} + +ControlPoints_SpawnsLoaded(&JSON: arrSpawns) { + assert (arrSpawns != Invalid_JSON) + + collectSpawnPoints(arrSpawns) +} + +ControlPoints_IsSpawnAvailable(const spawn[SpawnProps_s], TeamName: targetTeam) { + assert (targetTeam == TEAM_TERRORIST || targetTeam == TEAM_CT) + assert (spawn[sp_index] >= 0) + + new controlPoint = ___GetPointBySpawn(spawn[sp_index]) + if (!is_entity(controlPoint)) + return 1 + + // Point hasn't team + new TeamName: owningTeam = TeamName: pev(controlPoint, _pev_owningTeam) + // if (owningTeam != TEAM_TERRORIST && owningTeam != TEAM_CT) + // return 1 + + // Point has same team with wish player target + if (owningTeam == targetTeam) + return 1 + + return 0 +} + +___GetPointBySpawn(const spawnIdx) { + new entity = MaxClients + while ((entity = fm_find_ent_by_class(entity, g_pointClassname))) { + if (pev(entity, _pev_hPoint) != spawnIdx) + continue + + return entity + } + + return -1 +} + + +static collectSpawnPoints(&JSON: arrSpawns) { + new entity = MaxClients + while ((entity = fm_find_ent_by_class(entity, g_pointClassname))) { + engfunc(EngFunc_RemoveEntity, entity) + } + + new spawnCount = json_array_get_count(arrSpawns) + for (new spawnIdx; spawnIdx < spawnCount; spawnIdx++) { + new JSON: objSpawn = json_array_get_value(arrSpawns, spawnIdx) + + new spawn[SpawnProps_s] + GetSpawnFromObject(objSpawn, spawn) + + json_free(objSpawn) + + spawn[sp_origin][2] += SPAWN_POINT_OFFSET + + new entity = CTriggerAreaCapture__Spawn(spawn) + + set_pev(entity, _pev_hPoint, spawn[sp_index]) + } +} + +Point_SetRender(entity, renderState_s: _state = rs_Reset) { + switch (_state) { + case rs_Reset: { + fm_animate_entity(entity, ACT_IDLE) + + set_pev(entity, pev_rendermode, kRenderTransAlpha) + set_pev(entity, pev_renderfx, kRenderFxNone) + set_pev(entity, pev_renderamt, /* g_renderAmount / 3 */ 10.0) + set_pev(entity, pev_rendercolor, g_color[TEAM_SPECTATOR]) + } + case rs_Start: { + fm_animate_entity(entity, ACT_WALK, g_sequenceRate) + + set_pev(entity, pev_rendermode, kRenderTransColor) + set_pev(entity, pev_renderfx, kRenderFxGlowShell) + set_pev(entity, pev_renderamt, g_renderAmount / 3) + set_pev(entity, pev_rendercolor, g_color[TeamName: pev(entity, _pev_owningTeam)]) + } + case rs_inProgress: { + new Float: captureTime; pev(entity, _pev_captureTime, captureTime) + new Float: timeRemaining; pev(entity, _pev_timeRemaining, timeRemaining) + new TeamName: owningTeam = TeamName: pev(entity, _pev_owningTeam) + + new Float: percentage = ((captureTime - timeRemaining) / captureTime) * 100 + new Float: remainingPercentage = timeRemaining / captureTime + + static Float: renderColor[3] + xs_vec_mul_scalar(g_color[owningTeam], remainingPercentage, renderColor) + set_pev(entity, pev_rendercolor, renderColor) + + set_pev(entity, pev_renderamt, g_renderAmount * remainingPercentage) + + #if 1 + new TeamName: teamInZone = TeamName: pev(entity, _pev_teamInZone) + static bool: clearProgress[MAX_PLAYERS + 1] + for (new p = 1; p <= MaxClients; p++) { + if (!is_user_connected(p)) + continue + + if (clearProgress[p]) { + rg_send_bartime(p, 0, .observer = true) + } + + clearProgress[p] = true + if (!is_user_alive(p)) + continue + + if (get_member(p, m_iTeam) != teamInZone) + continue + + new Float: distance = fm_entity_range(entity, p) + if (distance > redm_capture_spawn_distance) + continue + + rg_send_bartime2(p, floatround(captureTime), (captureTime - timeRemaining), .observer = true) + clearProgress[p] = false + } + #endif + } + } +} + +CTriggerAreaCapture__Spawn(const spawn[SpawnProps_s]) { + new entity = fm_create_entity(g_pointBaseClassname) + set_pev(entity, pev_classname, g_pointClassname) + + Point_SetRender(entity, rs_Reset) + + engfunc(EngFunc_SetModel, entity, g_ControlPointsModels[spawn[sp_team]]) + engfunc(EngFunc_SetOrigin, entity, spawn[sp_origin]) + + dllfunc(DLLFunc_Spawn, entity) + + CTriggerAreaCapture__CaptureThink(entity) + // set_pev(entity, pev_nextthink, get_gametime() + AREA_THINK_TIME) + + // Prevent CBaseTrigger::InitTrigger() actions + set_pev(entity, pev_effects, pev(entity, pev_effects) & ~EF_NODRAW) + set_pev(entity, pev_angles, spawn[sp_angle]) + + + { + set_pev(entity, _pev_owningTeam, spawn[sp_team]) + set_pev(entity, _pev_captureTime, redm_capture_spawn_time) + + new canCapture[MAX_CAPTURE_TEAMS] + canCapture[TEAM_TERRORIST] = bool: spawn[sp_canCapture][TEAM_TERRORIST] + canCapture[TEAM_CT] = bool: spawn[sp_canCapture][TEAM_CT] + CED_SetArray(entity, "canCapture", canCapture, sizeof(canCapture)) + } + + + engfunc(EngFunc_SetSize, entity, Float: {-16.0, -16.0, -36.0}, Float: {16.0, 16.0, 36.0}) + + return entity +} + +#if 0 + CTriggerAreaCapture__KeyValue() { + + } + + CTriggerAreaCapture__Precache() { + + } + + CTriggerAreaCapture__IsActive() { + // return !m_bDisabled; + } +#endif + +// static Float: lastTouch[MAX_PLAYERS + 1] = { 0.0, ... } + + +public CTriggerAreaCapture__AreaTouch(const entity, const other) { + static classname[32]; pev(entity, pev_classname, classname, charsmax(classname)) + if (strcmp(classname, g_pointClassname) != 0) + return + + // server_print("-- CTriggerAreaCapture__AreaTouch(%i, %i)", entity, other) + + #if 0 + new Float: nextThink; pev(entity, pev_nextthink, nextThink) + if (nextThink - get_gametime() < (AREA_THINK_TIME - 0.1)) + return + #endif + + #if 0 + if (!IsActive()) + return + + if (!PointsMayBeCaptured()) + return + #endif + + // dont touch for non-alive or non-players + if (!ExecuteHamB(Ham_IsPlayer, other) || !is_user_alive(other)) + return + + new player = other + + new TeamName: owningTeam = TeamName: pev(entity, _pev_owningTeam) + new TeamName: playerTeam = TeamName: get_ent_data(player, "CBasePlayer", "m_iTeam") + if (playerTeam != owningTeam) { + #if 0 + new bool: canCaptureT = pev(entity, _pev_canCaptureT) + new bool: canCaptureCT = pev(entity, _pev_canCaptureCT) + + if (m_TeamData[playerTeam][bCanCap]) { + DisplayCapHintTo(player) + } + #endif + } + + #if 0 + new Float: diff = get_gametime() - lastTouch[player] + server_print("diff:%f", diff) + if (diff > AREA_THINK_TIME) { + server_print("--->> StartTouch") + } + + lastTouch[player] = get_gametime() + #endif + + #if 1 + { + set_hudmessage( + 255, 255, 255, + 0.6, 0.0, + 0, 0.0, + AREA_THINK_TIME + 0.1, + 0.0, + 0.0, + 1 + ) + + new Float: timeRemaining; pev(entity, _pev_timeRemaining, timeRemaining) + new Float: lastReductionTime; pev(entity, _pev_lastReductionTime, lastReductionTime) + new Float: captureTime; pev(entity, _pev_captureTime, captureTime) + + new bool: canCapture[MAX_CAPTURE_TEAMS] + CED_GetArray(entity, "canCapture", canCapture, sizeof(canCapture)) + + new buffer[512] + formatex(buffer, charsmax(buffer), "^n\ + (%.1f) touch `trigger_capture_area` ^n\ + %n -> %i ^n\ + ^n\ + %.1f | %-16.16s^n\ + %.1f | %-16.16s^n\ + %.1f | %-16.16s^n\ + %i | %-16.16s^n\ + %i | %-16.16s^n\ + %i | %-16.16s^n\ + %i | %-16.16s^n\ + %i | %-16.16s^n\ + %i | %-16.16s^n\ + ", + get_gametime(), other, entity, + timeRemaining, "timeRemaining", + lastReductionTime, "lastReductionTime", + captureTime, "captureTime", + + pev(entity, _pev_isCapturing), "isCapturing", + pev(entity, _pev_owningTeam), "owningTeam", + pev(entity, _pev_teamInZone), "teamInZone", + pev(entity, _pev_capturingTeam), "capturingTeam", + + canCapture[TEAM_TERRORIST], "canCapture[t]", + canCapture[TEAM_CT], "canCapture[ct]" + ) + + show_hudmessage(player, buffer) + // server_print(buffer) + } + #endif +} + +TeamplayGameRules__GetCaptureValueForPlayer(const player) { + #pragma unused player + return 1 +} + + +public CTriggerAreaCapture__CaptureThink(const entity) { + static classname[32]; pev(entity, pev_classname, classname, charsmax(classname)) + if (strcmp(classname, g_pointClassname) != 0) + return + + // server_print("-- (%.2f) CTriggerAreaCapture__CaptureThink(%i)", get_gametime(), entity) +#if 1 + // go through our list of players + new iNumPlayers[MAX_CAPTURE_TEAMS] + new iNumBlockablePlayers[MAX_CAPTURE_TEAMS] // Players in the zone who can't cap, but can block / pause caps + new pFirstPlayerTouching[MAX_CAPTURE_TEAMS] + + // Loop through the entities we're touching, and find players + // new m_hTouchingEntities[MAX_PLAYERS + 1] + for (new p = 1; p <= MaxClients; p++) { + if (!is_user_alive(p)) + continue + + new Float: distance = fm_entity_range(entity, p) + if (distance > redm_capture_spawn_distance) + continue + + new TeamName: team = TeamName: get_member(p, m_iTeam) + + dllfunc(DLLFunc_Touch, entity, p) + + #if 0 + // If a team's not allowed to cap a point, don't count players in it at all + if ( !TeamplayGameRules()->TeamMayCapturePoint( team, m_hPoint->GetPointIndex() ) ) + continue; + + if ( !TeamplayGameRules()->PlayerMayCapturePoint( pPlayer, m_hPoint->GetPointIndex() ) ) { + if ( TeamplayGameRules()->PlayerMayBlockPoint( pPlayer, m_hPoint->GetPointIndex() ) ) + { + if ( iNumPlayers[iTeam] == 0 && iNumBlockablePlayers[iTeam] == 0 ) + { + pFirstPlayerTouching[iTeam] = pPlayer + } + + iNumBlockablePlayers[iTeam] += TeamplayGameRules()->GetCaptureValueForPlayer( pPlayer ) + } + continue; + } + #endif + + if (team == TEAM_TERRORIST || team == TEAM_CT) { + if (iNumPlayers[team] == 0 && iNumBlockablePlayers[team] == 0) { + pFirstPlayerTouching[team] = p + } + + iNumPlayers[team] += TeamplayGameRules__GetCaptureValueForPlayer( p ) + } + + } + + new iTeamsInZone = 0 + // new bool: bUpdatePlayers = false + set_pev(entity, _pev_teamInZone, TEAM_UNASSIGNED) + + new m_TeamData[MAX_CAPTURE_TEAMS][perTeamData_s] + // pev(entity, _pev_teamData, m_TeamData, (_: MAX_CAPTURE_TEAMS * _: perTeamData_s)) + + for (new TeamName: t = TEAM_TERRORIST; t <= TEAM_CT; t++) { + iNumPlayers[t] *= mp_simulatemultiplecappers + + if (m_TeamData[t][iNumTouching] != iNumPlayers[t]) { + m_TeamData[t][iNumTouching] = iNumPlayers[t] + + // bUpdatePlayers = true + } + + m_TeamData[t][iBlockedTouching] = m_TeamData[t][iNumTouching] + if (m_TeamData[t][iNumTouching]) { + iTeamsInZone++ + + set_pev(entity, _pev_teamInZone, t) + } + + // set_pev(entity, _pev_teamData, m_TeamData) + } + + #if 1 + if (test) { + iTeamsInZone = 2 + } + #endif + + if (iTeamsInZone > 1) { + set_pev(entity, _pev_teamInZone, TEAM_UNASSIGNED) + } else { + // If we've got non-cappable, yet blockable players here for the team that's defending, they + // need to block the cap. This catches cases like the TF invulnerability, which needs to block + // caps, but isn't allowed to contribute to a cap. + for (new TeamName: t = TEAM_TERRORIST; t <= TEAM_CT; t++) { + if (!iNumBlockablePlayers[t] || TeamName: pev(entity, _pev_teamInZone) == t) + continue + + set_pev(entity, _pev_teamInZone, pev(entity, _pev_teamInZone) + 1) + } + } + + #if 0 + UpdateTeamInZone() + #endif + + // server_print("iNumPlayers:[%i|%i], iTeamsInZone:%i, m_TeamData[][iNumTouching]:[%i, %i] _pev_teamInZone:%i", + // iNumPlayers[TEAM_TERRORIST], iNumPlayers[TEAM_CT], + // iTeamsInZone, + // m_TeamData[TEAM_TERRORIST][iNumTouching], m_TeamData[TEAM_CT][iNumTouching], + // pev(entity, _pev_teamInZone) + // ) + +#endif + + + + + new Float: gametime = get_gametime() + set_pev(entity, pev_nextthink, gametime + AREA_THINK_TIME) + + // When a player blocks, tell them the cap index and attempt number + // only give successive blocks to them if the attempt number is different + if (pev(entity, _pev_isCapturing)) { + new TeamName: capturingTeam = TeamName: pev(entity, _pev_capturingTeam) + new TeamName: teamInZone = TeamName: pev(entity, _pev_teamInZone) + new TeamName: owningTeam = TeamName: pev(entity, _pev_owningTeam) + + new Float: lastReductionTime; pev(entity, _pev_lastReductionTime, lastReductionTime) + new Float: timeRemaining; pev(entity, _pev_timeRemaining, timeRemaining) + new Float: captureTime; pev(entity, _pev_captureTime, captureTime) + + // Calculate the amount of modification to the cap time + new Float: flTimeDelta = (gametime - lastReductionTime) + + #if 0 + new Float: old; pev(entity, pev_fov, old) + server_print("delta:%f, old:%f, new:%f", gametime-old, old, gametime) + set_pev(entity, pev_fov, (old = gametime)) + #endif + + + set_pev(entity, _pev_lastReductionTime, (lastReductionTime = gametime)) + + new Float: flReduction = flTimeDelta + new Float: flTotalTimeToCap = captureTime + + // Now remove the reduction amount after we've determined there's only 1 team in the area + if (capturingTeam == teamInZone) { + CTriggerAreaCapture__SetCapTimeRemaining(entity, timeRemaining - flReduction) + } else if (owningTeam == TEAM_UNASSIGNED && teamInZone != TEAM_UNASSIGNED ) { + CTriggerAreaCapture__SetCapTimeRemaining(entity, timeRemaining + flReduction) + } else { + // Caps deteriorate over time + #if 1 + new bool: TeamMayCapturePoint = true + if ( /* TeamplayRoundBasedRules() && m_hPoint && TeamplayRoundBasedRules()->TeamMayCapturePoint(m_nCapturingTeam,m_hPoint->GetPointIndex()) */ TeamMayCapturePoint ) { + new Float: flDecrease = (flTotalTimeToCap / redm_capture_capdeteriorate_time) * flTimeDelta + #if 0 + if (TeamplayRoundBasedRules() && TeamplayRoundBasedRules()->InOvertime()) { + flDecrease *= 6 + } + #endif + + CTriggerAreaCapture__SetCapTimeRemaining(entity, timeRemaining + flDecrease ) + } + else + #endif + { + // server_print("111111111") + CTriggerAreaCapture__SetCapTimeRemaining(entity, flTotalTimeToCap) + } + } + + pev(entity, _pev_timeRemaining, timeRemaining) + + #if 1 + { // TODO: team_control_point WORK + Point_SetRender(entity, rs_inProgress) + } + #endif + + // if the cap is done + if (timeRemaining <= 0.0) { + CTriggerAreaCapture__EndCapture(entity, capturingTeam) + return //we're done + } else { + // server_print(" :: Think: (diff:%f) timeRemaining:%f, flTotalTimeToCap:%f", + // (timeRemaining - flTotalTimeToCap), timeRemaining, flTotalTimeToCap + // ) + + if (timeRemaining >= flTotalTimeToCap) { + CTriggerAreaCapture__BreakCapture(entity, false ) + return + } + } + } else { + // If there are any teams in the zone that aren't the owner, try to start capping + new TeamName: teamInZone = TeamName: pev(entity, _pev_teamInZone) + if ((teamInZone != TEAM_TERRORIST && teamInZone != TEAM_CT)) + return + + new TeamName: owningTeam = TeamName: pev(entity, _pev_owningTeam) + + new bool: canCapture[MAX_CAPTURE_TEAMS] + CED_GetArray(entity, "canCapture", canCapture, sizeof(canCapture)) + + for (new TeamName: t = TEAM_TERRORIST; t <= TEAM_CT; t++) { + if (owningTeam == t) + continue + + // TODO: rework structure + if (!canCapture[t]) + continue + + // server_print("^n^nowningTeam:%i == t:%i, m_TeamData[][iNumTouching]:%i", + // owningTeam, t, + // m_TeamData[t][iNumTouching] + // ) + if (m_TeamData[t][iNumTouching] == 0) + continue + + if (mp_capstyle == 0 && m_TeamData[t][iNumTouching] < m_TeamData[t][iNumRequiredToCap]) + continue + + CTriggerAreaCapture__StartCapture(entity, t) + break + } + } +} + +CTriggerAreaCapture__StartCapture(const entity, const TeamName: team /* , const capmode */ ) { + // server_print("^n^n--> CTriggerAreaCapture__StartCapture(e:%i, t:%i)", entity, team) + + + #if 1 + { // TODO: team_control_point WORK + Point_SetRender(entity, rs_Start) + } + #endif + + set_pev(entity, _pev_capturingTeam, team) + new Float: captureTime; pev(entity, _pev_captureTime, captureTime) + + if (mp_capstyle == 1) { + // CTriggerAreaCapture__SetCapTimeRemaining( ((m_flCapTime * 2) * m_TeamData[team].iNumRequiredToCap) ); + } else { + CTriggerAreaCapture__SetCapTimeRemaining(entity, captureTime) + } + + set_pev(entity, _pev_isCapturing, true) + // m_bBlocked = false + + set_pev(entity, _pev_lastReductionTime, get_gametime()) +} + + +CTriggerAreaCapture__SetCapTimeRemaining(const entity, Float: time) +{ + // server_print(" :: SetCapTimeRemaining(%i, %f)", entity, time) + + set_pev(entity, _pev_timeRemaining, time) + + +#if 0 + new Float: flCapPercentage + + new TeamName: capturingTeam = TeamName: pev(entity, _pev_capturingTeam) + new Float: captureTime; pev(entity, _pev_captureTime, captureTime) + + new m_TeamData[MAX_CAPTURE_TEAMS][perTeamData_s] + pev(entity, _pev_teamData, m_TeamData, (_: MAX_CAPTURE_TEAMS * _: perTeamData_s)) + + + if (capturingTeam) { + flCapPercentage = time / captureTime + if (mp_capstyle == 1) { + flCapPercentage = time / ((captureTime * 2) * m_TeamData[capturingTeam][iNumRequiredToCap]) + } + } +#endif +} + + +CTriggerAreaCapture__EndCapture(const entity, const TeamName: team) { + set_pev(entity, _pev_owningTeam, team) + + set_pev(entity, _pev_isCapturing, false) + set_pev(entity, _pev_capturingTeam, TEAM_UNASSIGNED) + + CTriggerAreaCapture__SetCapTimeRemaining(entity, 0.0) + + #if 1 + { // TODO: team_control_point WORK + Point_SetRender(entity, rs_Reset) + + new TeamName: owningTeam = TeamName: pev(entity, _pev_owningTeam) + engfunc(EngFunc_SetModel, entity, g_ControlPointsModels[owningTeam]) + } + #endif + + // server_print("<-- CTriggerAreaCapture__EndCapture(e:%i, t:%i)", entity, team) +} + +CTriggerAreaCapture__BreakCapture(const entity, const bool: notEnoughPlayers) { + #pragma unused notEnoughPlayers + #if 1 + { // TODO: team_control_point WORK + Point_SetRender(entity, rs_Reset) + } + #endif + + if (pev(entity, _pev_isCapturing)) { + set_pev(entity, _pev_isCapturing, false) + set_pev(entity, _pev_capturingTeam, TEAM_UNASSIGNED) + + CTriggerAreaCapture__SetCapTimeRemaining(entity, 0.0) + } + + // server_print("<-- CTriggerAreaCapture__BreakCapture(e:%i)", entity) +} diff --git a/cstrike/addons/amxmodx/scripting/redm_spawns.sma b/cstrike/addons/amxmodx/scripting/redm_spawns.sma index 7e91620..c2de743 100644 --- a/cstrike/addons/amxmodx/scripting/redm_spawns.sma +++ b/cstrike/addons/amxmodx/scripting/redm_spawns.sma @@ -2,17 +2,24 @@ #include #include #include +#include #include #include #include #include +#include + +////////////////////////////////////////////////// +// #define DEBUG_SPAWNS_TRANCUATE_COUNT 20 + static MENU_FLAG = ADMIN_MAP static g_mapName[MAX_MAPNAME_LENGTH] -static const g_spawnClassname[] = "view_spawn" +static const g_baseSpawnClassname[] = "info_target" +static const g_spawnClassname[] = "info_deathmatch_spawn" static bool: g_editorEnabled = false @@ -22,27 +29,40 @@ enum HullVacant_s { hull_Invalid, } +enum _: SpawnProps_s { + sp_index, + Float: sp_origin[3], + Float: sp_angle[3], + Float: sp_vAngle[3], + TeamName: sp_team, + sp_group[32], + bool: sp_canCapture[TeamName], +} + enum EditorProps_s { - ep_team, - ep_focusEntity, ep_gravityPreset, - ep_group[32] + ep_spawn[SpawnProps_s], } + static g_editorProps[MAX_PLAYERS + 1][EditorProps_s] static JSON: g_arrSpawns = Invalid_JSON -static const g_spawnViewModels[_: TeamName - 1][] = { +static const g_spawnViewModels[TeamName][] = { "models/player/vip/vip.mdl", "models/player/leet/leet.mdl", "models/player/gign/gign.mdl", + + "" // TeamName compability } -static const g_teamName[_: TeamName - 1][] = { +static const g_teamName[TeamName][] = { "ANY", "T", "CT", + + "" // TeamName compability } static Float: g_gravityValues[] = { @@ -53,12 +73,14 @@ static redm_randomspawn static redm_randomspawn_los static Float: redm_randomspawn_dist -static bool: mp_freeforall +new bool: mp_freeforall + +#include "ReDeathmatch/Features/ControlPoints.inc" public plugin_precache() { - for (new i = 0; i < sizeof(g_spawnViewModels); i++) { - precache_model(g_spawnViewModels[i]) + for (new i = 0; i < (sizeof(g_spawnViewModels) - 1); i++) { + precache_model(g_spawnViewModels[TeamName: i]) } } @@ -82,6 +104,8 @@ public plugin_init() { Create_Convars() ROGInitialize(.MinDistance = 150.0) + + CapturePoints_Init() } public plugin_cfg() { @@ -194,13 +218,15 @@ static Editor_ReloadSpawns() { json_free(g_arrSpawns) g_arrSpawns = Editor_LoadSpawns() + + ControlPoints_SpawnsLoaded(g_arrSpawns) } static Editor_ResetProps(const player) { - g_editorProps[player][ep_focusEntity] = FM_NULLENT - g_editorProps[player][ep_team] = 0 g_editorProps[player][ep_gravityPreset] = 0 - g_editorProps[player][ep_group][0] = EOS + g_editorProps[player][ep_spawn][sp_index] = NULLENT + g_editorProps[player][ep_spawn][sp_team] = TEAM_UNASSIGNED + g_editorProps[player][ep_spawn][sp_group][0] = EOS } public CBasePlayer_UseEmpty(const player) { @@ -219,7 +245,7 @@ public CBasePlayer_UseEmpty(const player) { static Editor_Focus(const player) { new entity = FindEntityByAim(player) if (entity != FM_NULLENT) { - if (g_editorProps[player][ep_focusEntity] != FM_NULLENT) + if (g_editorProps[player][ep_spawn][sp_index] != FM_NULLENT) Editor_ClearEntityFocus(player) Editor_SetEntityFocus(player, entity) @@ -227,7 +253,7 @@ static Editor_Focus(const player) { return } - entity = g_editorProps[player][ep_focusEntity] + entity = g_editorProps[player][ep_spawn][sp_index] if (entity != FM_NULLENT) { Editor_ClearEntityFocus(player) } @@ -279,26 +305,28 @@ static bool: Editor_SetEntityFocus(const player, const entity = FM_NULLENT) { .amount = 20 ) - g_editorProps[player][ep_focusEntity] = entity - g_editorProps[player][ep_team] = pev(entity, pev_team) + g_editorProps[player][ep_spawn][sp_index] = entity + g_editorProps[player][ep_spawn][sp_team] = TeamName: pev(entity, pev_team) + g_editorProps[player][ep_spawn][sp_canCapture][TEAM_TERRORIST] = pev(entity, pev_iuser2) + g_editorProps[player][ep_spawn][sp_canCapture][TEAM_CT] = pev(entity, pev_iuser3) pev(entity, pev_netname, - g_editorProps[player][ep_group], - charsmax(g_editorProps[][ep_group]) + g_editorProps[player][ep_spawn][sp_group], + 31 ) return true } static Editor_ClearEntityFocus(const player) { - new entity = g_editorProps[player][ep_focusEntity] + new entity = g_editorProps[player][ep_spawn][sp_index] new bool: inDuck = bool: (pev(entity, pev_flags) & FL_DUCKING) fm_animate_entity(entity, inDuck ? ACT_CROUCHIDLE : ACT_IDLE) fm_set_rendering(entity) - g_editorProps[player][ep_focusEntity] = FM_NULLENT - g_editorProps[player][ep_group][0] = EOS + g_editorProps[player][ep_spawn][sp_index] = FM_NULLENT + g_editorProps[player][ep_spawn][sp_group][0] = EOS } static Menu_Editor(const player/* , const level */) { @@ -314,7 +342,7 @@ static Menu_Editor(const player/* , const level */) { if (!callback) callback = menu_makecallback("MenuCallback_Editor") - new focusEntity = g_editorProps[player][ep_focusEntity] + new focusEntity = g_editorProps[player][ep_spawn][sp_index] new spawnIndex = -1 if (focusEntity != FM_NULLENT) @@ -332,13 +360,25 @@ static Menu_Editor(const player/* , const level */) { .callback = callback ) - new team = g_editorProps[player][ep_team] + new TeamName: team = g_editorProps[player][ep_spawn][sp_team] menu_additem(menu, fmt("Team: %s", g_teamName[team])) menu_additem(menu, "Teleport", .callback = callback) menu_additem(menu, "Delete", .callback = callback) new gravityPreset = g_editorProps[player][ep_gravityPreset] menu_additem(menu, fmt("Gravity: %.2f", g_gravityValues[gravityPreset]), .callback = callback) - menu_additem(menu, fmt("Group: %s", g_editorProps[player][ep_group]), .callback = callback) + // menu_additem(menu, fmt("Group: %s", g_editorProps[player][ep_spawn][sp_group]), .callback = callback) + menu_additem(menu, + fmt("Can capture by T: %s", + g_editorProps[player][ep_spawn][sp_canCapture][TEAM_TERRORIST] ? "YES" : "\rNO\w" + ), + .callback = callback + ) + menu_additem(menu, + fmt("Can capture by CT: %s", + g_editorProps[player][ep_spawn][sp_canCapture][TEAM_CT] ? "YES" : "\rNO\w" + ), + .callback = callback + ) new stats[_: TeamName - 1] new total = Editor_GetStats(stats) @@ -358,7 +398,7 @@ static Menu_Editor(const player/* , const level */) { public MenuCallback_Editor(const player, const menu, const item) { switch(item) { case 2, 3: { - if (g_editorProps[player][ep_focusEntity] == FM_NULLENT) + if (g_editorProps[player][ep_spawn][sp_index] == FM_NULLENT) return ITEM_DISABLED } } @@ -373,9 +413,10 @@ public MenuHandler_Editor(const player, const menu, const item) { return PLUGIN_HANDLED } + new entity = g_editorProps[player][ep_spawn][sp_index] + switch (item) { case 0: { - new entity = g_editorProps[player][ep_focusEntity] if (entity != FM_NULLENT) { Spawn_Update(player, entity) } else { @@ -383,25 +424,21 @@ public MenuHandler_Editor(const player, const menu, const item) { } } case 1: { - ++g_editorProps[player][ep_team] - g_editorProps[player][ep_team] %= sizeof(g_spawnViewModels) - - new entity = g_editorProps[player][ep_focusEntity] + g_editorProps[player][ep_spawn][sp_team]++ + g_editorProps[player][ep_spawn][sp_team] = g_editorProps[player][ep_spawn][sp_team] % TeamName: (sizeof(g_spawnViewModels) - 1) + + new entity = g_editorProps[player][ep_spawn][sp_index] if (entity != FM_NULLENT) - Spawn_SetTeam(player, entity, g_editorProps[player][ep_team]) + Spawn_SetTeam(player, entity, g_editorProps[player][ep_spawn][sp_team]) } case 2: { - new entity = g_editorProps[player][ep_focusEntity] - new Float: origin[3], Float: angle[3], Float: vAngle[3] Spawn_EntityGetPosition(entity, origin, angle, vAngle) Spawn_EntitySetPosition(player, origin, angle, vAngle) } case 3: { - new entity = g_editorProps[player][ep_focusEntity] - Spawn_Delete(entity) - g_editorProps[player][ep_focusEntity] = FM_NULLENT + g_editorProps[player][ep_spawn][sp_index] = FM_NULLENT } case 4: { ++g_editorProps[player][ep_gravityPreset] @@ -413,8 +450,18 @@ public MenuHandler_Editor(const player, const menu, const item) { g_gravityValues[g_editorProps[player][ep_gravityPreset]] ) } + // case 5: { + // client_cmd(player, "messagemode enter_spawnGroup") + // } case 5: { - client_cmd(player, "messagemode enter_spawnGroup") + g_editorProps[player][ep_spawn][sp_canCapture][TEAM_TERRORIST] ^= 1 + if (entity != FM_NULLENT) + set_pev(entity, pev_iuser2, g_editorProps[player][ep_spawn][sp_canCapture][TEAM_TERRORIST]) + } + case 6: { + g_editorProps[player][ep_spawn][sp_canCapture][TEAM_CT] ^= 1 + if (entity != FM_NULLENT) + set_pev(entity, pev_iuser3, g_editorProps[player][ep_spawn][sp_canCapture][TEAM_CT]) } } @@ -436,9 +483,9 @@ public ClCmd_EnterSpawnGroup(const player, const level, const commandId) { return PLUGIN_HANDLED } - copy(g_editorProps[player][ep_group], charsmax(g_editorProps[][ep_group]), spawnGroup) + copy(g_editorProps[player][ep_spawn][sp_group], 31, spawnGroup) - new entity = g_editorProps[player][ep_focusEntity] + new entity = g_editorProps[player][ep_spawn][sp_index] if (entity != FM_NULLENT) set_pev(entity, pev_netname, spawnGroup) @@ -463,27 +510,36 @@ static Editor_GetStats(stats[_: TeamName - 1]) { } static bool: Spawn_Add(const player) { - new Float: origin[3], Float: angle[3], Float: vAngle[3] - Spawn_EntityGetPosition(player, origin, angle, vAngle) - origin[2] += 0.1 + new spawn[SpawnProps_s] + GetSpawnFromPEV(player, spawn) - new team = g_editorProps[player][ep_team] + xs_vec_add(spawn[sp_origin], Float: { 0.0, 0.0, 0.1 }, spawn[sp_origin]) + spawn[sp_team] = TeamName: g_editorProps[player][ep_spawn][sp_team] + copy(spawn[sp_group], charsmax(spawn[sp_group]), g_editorProps[player][ep_spawn][sp_group]) + spawn[sp_canCapture][TEAM_TERRORIST] = g_editorProps[player][ep_spawn][sp_canCapture][TEAM_TERRORIST] + spawn[sp_canCapture][TEAM_CT] = g_editorProps[player][ep_spawn][sp_canCapture][TEAM_CT] - return Add(origin, angle, vAngle, team, g_editorProps[player][ep_group]) + new entity = CSpawnPoint_Spawn(spawn) + return is_entity(entity) } -static bool: Add(const Float: origin[3], const Float: angle[3], const Float: vAngle[3], const team, const group[]) { - new entity = Spawn_CreateEntity() - if (!Spawn_EntitySetPosition(entity, origin, angle, vAngle)) { +static CSpawnPoint_Spawn(const spawn[SpawnProps_s]) { + new entity = fm_create_entity(g_baseSpawnClassname) + set_pev(entity, pev_classname, g_spawnClassname) + + new HullVacant_s: res = CheckHullVacant(spawn[sp_origin], .ignoreEnt = entity, .ignorePlayers = g_editorEnabled) + if (res == hull_Invalid) { + client_print_color(0, print_team_red, "^3Invalid place!^1") + Spawn_Delete(entity) - return false + return NULLENT } - set_pev(entity, pev_team, team) - set_pev(entity, pev_netname, group) - engfunc(EngFunc_SetModel, entity, g_spawnViewModels[team]) + set_pev(entity, pev_solid, SOLID_BBOX) - return true + SetSpawnToPEV(entity, spawn) + + return entity } static Spawn_Update(const player, const entity) { @@ -498,7 +554,7 @@ static Spawn_Delete(const entity) { engfunc(EngFunc_RemoveEntity, entity) } -static Spawn_SetTeam(const player, const entity, const team) { +static Spawn_SetTeam(const player, const entity, const TeamName: team) { set_pev(entity, pev_team, team) engfunc(EngFunc_SetModel, entity, g_spawnViewModels[team]) @@ -539,16 +595,7 @@ static bool: Spawn_EntitySetPosition(const entity, const Float: origin[3], const return true } -static Spawn_CreateEntity() { - new entity = fm_create_entity("info_target") - - set_pev(entity, pev_classname, g_spawnClassname) - set_pev(entity, pev_solid, SOLID_BBOX) - - return entity -} - -static stock fm_animate_entity(const entity, const Activity: sequence = ACT_IDLE, const Float: framerate = 0.0) { +stock fm_animate_entity(const entity, const Activity: sequence = ACT_IDLE, const Float: framerate = 0.0) { set_pev(entity, pev_sequence, sequence) set_pev(entity, pev_framerate, framerate) } @@ -585,34 +632,16 @@ static Editor_AddViewSpawns(const JSON: arrSpawns) { } for (new idx, size = json_array_get_count(arrSpawns); idx < size; idx++) { - new JSON: spawn = json_array_get_value(arrSpawns, idx) - - new Float: origin[3], Float: angle[3], Float: vAngle[3] - new team = json_object_get_number(spawn, "team") - new JSON: arrOrigin = json_object_get_value(spawn, "origin") - new JSON: arrAngle = json_object_get_value(spawn, "angle") - new JSON: arrVAngle = json_object_get_value(spawn, "vAngle") - - new group[32] - json_object_get_string(spawn, "group", group, charsmax(group)) - - for (new i; i < sizeof(origin); i++) { - origin[i] = json_array_get_real(arrOrigin, i) - if (i < 2) { - angle[i] = json_array_get_real(arrAngle, i) - vAngle[i] = json_array_get_real(arrVAngle, i) - } - } + new JSON: objSpawn = json_array_get_value(arrSpawns, idx) + + new spawn[SpawnProps_s] + GetSpawnFromObject(objSpawn, spawn) - if (!Add(origin, angle, vAngle, team, group)) { + if (!CSpawnPoint_Spawn(spawn)) { LogMessageEx(Warning, "Editor_AddViewSpawns: Can't add spawn `%i`!", idx) } - json_free(arrOrigin) - json_free(arrAngle) - json_free(arrVAngle) - - json_free(spawn) + json_free(objSpawn) } } @@ -621,42 +650,23 @@ static Editor_SaveSpawns() { new entity = FM_NULLENT while ((entity = fm_find_ent_by_class(entity, g_spawnClassname))) { - new JSON: spawn = json_init_object() + new spawn[SpawnProps_s] + spawn[sp_index] = json_array_get_count(arrSpawns) - new Float: origin[3], Float: angle[3], Float: vAngle[3] - pev(entity, pev_origin, origin) - pev(entity, pev_angles, angle) - pev(entity, pev_v_angle, vAngle) - new team = pev(entity, pev_team) - new group[32] - pev(entity, pev_netname, group, charsmax(group)) + pev(entity, pev_origin, spawn[sp_origin]) + pev(entity, pev_angles, spawn[sp_angle]) + pev(entity, pev_v_angle, spawn[sp_vAngle]) + pev(entity, pev_netname, spawn[sp_group], charsmax(spawn[sp_group])) + spawn[sp_team] = TeamName: pev(entity, pev_team) - json_object_set_number(spawn, "team", team) - json_object_set_string(spawn, "group", group) + spawn[sp_canCapture][TEAM_TERRORIST] = pev(entity, pev_iuser2) + spawn[sp_canCapture][TEAM_CT] = pev(entity, pev_iuser3) - new JSON: arrOrigin = json_init_array() - new JSON: arrAngle = json_init_array() - new JSON: arrVAngle = json_init_array() - - for (new i; i < 3; i++) { - json_array_append_real(arrOrigin, origin[i]) - if (i < 2) { - json_array_append_real(arrAngle, angle[i]) - json_array_append_real(arrVAngle, vAngle[i]) - } - } - - json_object_set_value(spawn, "origin", arrOrigin) - json_object_set_value(spawn, "angle", arrAngle) - json_object_set_value(spawn, "vAngle", arrVAngle) + new JSON: objSpawn = json_init_object() + SetSpawnToObject(spawn, objSpawn) + json_array_append_value(arrSpawns, objSpawn) - json_free(arrOrigin) - json_free(arrAngle) - json_free(arrVAngle) - - json_array_append_value(arrSpawns, spawn) - - json_free(spawn) + json_free(objSpawn) } new JSON: objSpawns = json_init_object() @@ -701,6 +711,10 @@ static JSON: Editor_LoadSpawns() { new JSON: arrSpawns = json_object_get_value(objSpawns, "spawns") +#if defined DEBUG_SPAWNS_TRANCUATE_COUNT + while(json_array_remove(arrSpawns, DEBUG_SPAWNS_TRANCUATE_COUNT)) { } +#endif + LogMessageEx(Debug, "Editor_LoadSpawns: Map `%s` total spawns: %i loaded.", g_mapName, json_array_get_count(arrSpawns) ) @@ -960,7 +974,7 @@ public bool: SpawnPreset_DefaultPreset(const player) { } static bool: Player_MoveToSpawn(const player, const count) { - new team = mp_freeforall ? 0 : get_member(player, m_iTeam) + new TeamName: targetTeam = mp_freeforall ? TEAM_UNASSIGNED : get_member(player, m_iTeam) new bestSpawnIdx = -1 for (new attempt; attempt <= 2 && bestSpawnIdx == -1; attempt++) { @@ -975,8 +989,14 @@ static bool: Player_MoveToSpawn(const player, const count) { potentialSpawnIdx = 0 } - // TODO: optimize that! - new spawnResult = Spawn_CheckConditions(player, team, potentialSpawnIdx, attempt) + // TODO: optimize that! + + new JSON: objSpawn = json_array_get_value(g_arrSpawns, potentialSpawnIdx) + new spawn[SpawnProps_s] + GetSpawnFromObject(objSpawn, spawn) + json_free(objSpawn) + new spawnResult = Spawn_CheckConditions(player, targetTeam, spawn, attempt) + if (spawnResult == 0) { bestSpawnIdx = potentialSpawnIdx break @@ -987,39 +1007,66 @@ static bool: Player_MoveToSpawn(const player, const count) { } if (bestSpawnIdx == -1) { - LogMessageEx(Warning, "Player %n can't found good spawn point", + #if 0 + /* + TODO: it doesn't work yet, but need to implement + + Idea: if at the moment there is no + suitable point for trespawn - make a delayed + respawn for 0.5..2.0 seconds. + */ + { + new cantFoundSpawnMethod = 0 + if (cantFoundSpawnMethod) { + LogMessageEx(Warning, "Player `%n` can't find a good (suitable) spawn point and will therefore be spawned later (for 3 seconds).", + player + ) + + set_member(player, m_flRespawnPending, get_gametime() + 3.0) + + return true + } + } + #endif + + LogMessageEx(Warning, "Player `%n` can't find a good (suitable) spawn point and so uses the standard map respawn.", player ) return false } - new Float: origin[3], Float: angle[3], Float: vAngle[3] - GetSpawnFromObject(bestSpawnIdx, origin, angle, vAngle) - new bool: res = Spawn_EntitySetPosition(player, origin, angle, vAngle) + new JSON: objSpawn = json_array_get_value(g_arrSpawns, bestSpawnIdx) + new spawn[SpawnProps_s] + GetSpawnFromObject(objSpawn, spawn) + + json_free(objSpawn) + + new bool: res = Spawn_EntitySetPosition(player, spawn[sp_origin], spawn[sp_angle], spawn[sp_vAngle]) if (!res) { LogMessageEx(Debug, "Player %n can't use a spawn point %i[%.2f,%.2f,%.2f]", player, bestSpawnIdx, - origin[0], origin[1], origin[2] + spawn[sp_origin][0], spawn[sp_origin][1], spawn[sp_origin][2] ) } return res } -static Spawn_CheckConditions(const target, const targetTeam, const spawnIdx, const attempt) { - new Float: spawnOrigin[3], Float: spawnAngle[3], Float: spawnVAngle[3], spawnTeam, spawnGroup[32] - GetSpawnFromObject(spawnIdx, spawnOrigin, spawnAngle, spawnVAngle, spawnTeam, spawnGroup) +static Spawn_CheckConditions(const target, const TeamName: targetTeam, const spawn[SpawnProps_s], const attempt) { + new bool: isSpawnAvailable = ControlPoints_IsSpawnAvailable(spawn, targetTeam) != 0 + // server_print("------- attempt:#%i, isSpawnAvailable:%i", + // attempt, isSpawnAvailable + // ) - // Doesn't match because of the spawn team - if (targetTeam && spawnTeam && (spawnTeam != targetTeam)) + if (!isSpawnAvailable) return 1 // TODO: implement spawn group check // if (spawnGroup[0] != EOS && spawnGroup[0] == 'A') // return false - new HullVacant_s: res = CheckHullVacant(spawnOrigin, .ignoreEnt = target, .ignorePlayers = false) + new HullVacant_s: res = CheckHullVacant(spawn[sp_origin], .ignoreEnt = target, .ignorePlayers = false) if (res == hull_Invalid) return 2 @@ -1037,7 +1084,7 @@ static Spawn_CheckConditions(const target, const targetTeam, const spawnIdx, con new Float: enemyOrigin[3] pev(i, pev_origin, enemyOrigin) - new Float: disatanceToEnemy = get_distance_f(spawnOrigin, enemyOrigin) + new Float: disatanceToEnemy = get_distance_f(spawn[sp_origin], enemyOrigin) new Float: searchDistance = redm_randomspawn_dist / (attempt + 1) if (disatanceToEnemy < 200.0) @@ -1047,11 +1094,11 @@ static Spawn_CheckConditions(const target, const targetTeam, const spawnIdx, con continue new Float: spawnHeadOrigin[3] - spawnHeadOrigin = spawnOrigin + xs_vec_add(spawnHeadOrigin, Float: { 0.0, 0.0, 17.0 }, spawnHeadOrigin) spawnHeadOrigin[2] + 17.0 // check the head if (redm_randomspawn_los) { - if (/* fm_is_in_viewcone(i, spawnOrigin) && */ fm_is_visible(i, spawnHeadOrigin, true)) { + if (/* fm_is_in_viewcone(i, spawn[sp_origin]) && */ fm_is_visible(i, spawnHeadOrigin, true)) { return 4 } } @@ -1060,26 +1107,104 @@ static Spawn_CheckConditions(const target, const targetTeam, const spawnIdx, con return 0 } -static GetSpawnFromObject(const spawnIdx, Float: origin[3], Float: angle[3], Float: vAngle[3], &team = 0, spawnGroup[] = "") { - new JSON: spawn = json_array_get_value(g_arrSpawns, spawnIdx) - team = json_object_get_number(spawn, "team") - new JSON: arrOrigin = json_object_get_value(spawn, "origin") - new JSON: arrAngle = json_object_get_value(spawn, "angle") - new JSON: arrVAngle = json_object_get_value(spawn, "vAngle") - json_object_get_string(spawn, "group", spawnGroup, 31) - - for (new i; i < sizeof(origin); i++) { - origin[i] = json_array_get_real(arrOrigin, i) - if (i < 2) { - angle[i] = json_array_get_real(arrAngle, i) - vAngle[i] = json_array_get_real(arrVAngle, i) +JSON: SetSpawnToObject(const spawn[SpawnProps_s], &JSON: objSpawn) { + assert(objSpawn != Invalid_JSON) + + + json_object_set_number(objSpawn, "index", spawn[sp_index]) + json_object_set_number(objSpawn, "team", _: spawn[sp_team]) + json_object_set_string(objSpawn, "group", spawn[sp_group]) + + { + new JSON: arrOrigin = json_init_array() + new JSON: arrAngle = json_init_array() + new JSON: arrVAngle = json_init_array() + + for (new i; i < 3; i++) { + json_array_append_real(arrOrigin, spawn[sp_origin][i]) + if (i < 2) { + json_array_append_real(arrAngle, spawn[sp_angle][i]) + json_array_append_real(arrVAngle, spawn[sp_vAngle][i]) + } } + + json_object_set_value(objSpawn, "origin", arrOrigin) + json_object_set_value(objSpawn, "angle", arrAngle) + json_object_set_value(objSpawn, "vAngle", arrVAngle) + + json_free(arrOrigin) + json_free(arrAngle) + json_free(arrVAngle) + } + + json_object_set_bool(objSpawn, "capture.canCaptureT", bool: spawn[sp_canCapture][TEAM_TERRORIST], .dot_not = true) + json_object_set_bool(objSpawn, "capture.canCaptureCT", bool: spawn[sp_canCapture][TEAM_CT], .dot_not = true) +} + +GetSpawnFromObject(const &JSON: objSpawn, spawn[SpawnProps_s]) { + assert(objSpawn != Invalid_JSON) + + spawn[sp_index] = json_object_get_number(objSpawn, "index") + spawn[sp_team] = TeamName: json_object_get_number(objSpawn, "team") + json_object_get_string(objSpawn, "group", spawn[sp_group], charsmax(spawn[sp_group])) + + { + new JSON: arrOrigin = json_object_get_value(objSpawn, "origin") + new JSON: arrAngle = json_object_get_value(objSpawn, "angle") + new JSON: arrVAngle = json_object_get_value(objSpawn, "vAngle") + + for (new i; i < sizeof(spawn[sp_origin]); i++) { + spawn[sp_origin][i] = json_array_get_real(arrOrigin, i) + if (i < 2) { + spawn[sp_angle][i] = json_array_get_real(arrAngle, i) + spawn[sp_vAngle][i] = json_array_get_real(arrVAngle, i) + } + } + + json_free(arrOrigin) + json_free(arrAngle) + json_free(arrVAngle) } - json_free(arrOrigin) - json_free(arrAngle) - json_free(arrVAngle) - json_free(spawn) + spawn[sp_canCapture][TEAM_TERRORIST] = json_object_get_bool(objSpawn, "capture.canCaptureT", .dot_not = true) + spawn[sp_canCapture][TEAM_CT] = json_object_get_bool(objSpawn, "capture.canCaptureCT", .dot_not = true) +} + +SetSpawnToPEV(const entity, const spawn[SpawnProps_s]) { + assert (is_entity(entity)) + + set_pev(entity, pev_iuser1, spawn[sp_index]) + set_pev(entity, pev_origin, spawn[sp_origin]) + set_pev(entity, pev_angles, spawn[sp_angle]) + set_pev(entity, pev_v_angle, spawn[sp_vAngle]) + set_pev(entity, pev_team, spawn[sp_team]) + set_pev(entity, pev_netname, spawn[sp_group]) + + set_pev(entity, pev_iuser2, spawn[sp_canCapture][TEAM_TERRORIST]) + set_pev(entity, pev_iuser3, spawn[sp_canCapture][TEAM_CT]) + + // + engfunc(EngFunc_SetOrigin, entity, spawn[sp_origin]) + engfunc(EngFunc_SetModel, entity, g_spawnViewModels[spawn[sp_team]]) + + set_pev(entity, pev_fixangle, 1) + set_pev(entity, pev_velocity, Float: {0.0, 0.0, 0.0}) + set_pev(entity, pev_punchangle, Float: {0.0, 0.0, 0.0}) + set_pev(entity, pev_avelocity, Float: {0.0, 0.0, 0.0}) +} + +GetSpawnFromPEV(const entity, spawn[SpawnProps_s]) { + assert (is_entity(entity)) + + spawn[sp_index] = pev(entity, pev_iuser1) + pev(entity, pev_origin, spawn[sp_origin]) + pev(entity, pev_angles, spawn[sp_angle]) + pev(entity, pev_v_angle, spawn[sp_vAngle]) + spawn[sp_team] = TeamName: pev(entity, pev_team) + pev(entity, pev_netname, spawn[sp_group], charsmax(spawn[sp_group])) + + spawn[sp_canCapture][TEAM_TERRORIST] = pev(entity, pev_iuser2) + spawn[sp_canCapture][TEAM_CT] = pev(entity, pev_iuser3) } // TODO: for optimize