Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 208 additions & 0 deletions 2001/ze_maontain_escape/bicycle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
import { Instance, CSInputs } from "cs_script/point_script";

// 配置参数
const UPDATE_INTERVAL = 0.2; // 更新间隔(秒)
const MAX_SPEED_FWD = 250; // 前进最大速度
const MAX_SPEED_REV = MAX_SPEED_FWD / 4; // 倒车最大速度(前进的1/4)
const TURN_RATE = 100; // 最大转向角速度(度/秒)
const ACCELERATION = 1500; // 水平加速度(单位/秒²)

// 爬坡辅助参数
const HILL_ASSIST_ANGLE_MIN = -75; // 触发爬坡辅助的最小 pitch 角度
const HILL_ASSIST_ANGLE_MAX = 30; // 触发爬坡辅助的最大 pitch 角度
const HILL_ASSIST_MAX_SPEED = 35; // 爬坡辅助最大附加速度

// 侧翻修正参数
const ROLL_THRESHOLD = 90; // 触发侧翻修正的 roll 绝对值阈值
const ROLL_CORRECTION_RATE = 300; // 最大侧翻修正角速度

// 脚本状态
let bicycleBox = null; // 当前自行车实体
let riderPawn = null; // 当前骑手玩家
let isRiding = false; // 是否正在骑行
let thinkRegistered = false; // think 是否已注册

// 按键按下时间记录(用于优先级处理)
let keyTimers = {};

// 辅助函数:提取 namefix 后缀
function getSuffixFromName(name) { return name.match(/_(\d+)$/)?.[1] || null; }

// 辅助函数:检查实体是否有效
function isValidEntity(ent) { return ent && ent.IsValid(); }

// 辅助函数:计算前向向量(基于 QAngle)
function getForwardVector(angles) {
const pitch = angles.pitch * Math.PI / 180;
const yaw = angles.yaw * Math.PI / 180;
const cosPitch = Math.cos(pitch);
return {
x: Math.cos(yaw) * cosPitch,
y: Math.sin(yaw) * cosPitch,
z: -Math.sin(pitch)
};
}

// 辅助函数:限制水平速度变化量(平滑加速/减速)
function applyAcceleration(current, target, maxDelta) {
const diff = { x: target.x - current.x, y: target.y - current.y };
const len = Math.sqrt(diff.x * diff.x + diff.y * diff.y);
if (len <= maxDelta) return target;
const scale = maxDelta / len;
return { x: current.x + diff.x * scale, y: current.y + diff.y * scale };
}

// 主更新循环
function think() {
if (!isRiding) { Instance.SetNextThink(Instance.GetGameTime() + UPDATE_INTERVAL); return; }

// 检查实体有效性,失效时自动停止骑行
if (!isValidEntity(bicycleBox) || !isValidEntity(riderPawn) || !riderPawn.IsAlive()) {
isRiding = false;
return;
}

// 1. 将骑手位置锁定到自行车位置
riderPawn.Teleport({ position: bicycleBox.GetAbsOrigin() });

// 2. 获取当前时间
const currentTime = Instance.GetGameTime();

// 3. 检查按键状态
const pressedForward = riderPawn.IsInputPressed(CSInputs.FORWARD);
const pressedBack = riderPawn.IsInputPressed(CSInputs.BACK);
const pressedLeft = riderPawn.IsInputPressed(CSInputs.LEFT);
const pressedRight = riderPawn.IsInputPressed(CSInputs.RIGHT);

// 更新按键按下时间(仅刚按下时记录)
if (riderPawn.WasInputJustPressed(CSInputs.FORWARD)) keyTimers[CSInputs.FORWARD] = currentTime;
if (riderPawn.WasInputJustPressed(CSInputs.BACK)) keyTimers[CSInputs.BACK] = currentTime;
if (riderPawn.WasInputJustPressed(CSInputs.LEFT)) keyTimers[CSInputs.LEFT] = currentTime;
if (riderPawn.WasInputJustPressed(CSInputs.RIGHT)) keyTimers[CSInputs.RIGHT] = currentTime;

// 键释放时清除时间戳
if (!pressedForward) keyTimers[CSInputs.FORWARD] = null;
if (!pressedBack) keyTimers[CSInputs.BACK] = null;
if (!pressedLeft) keyTimers[CSInputs.LEFT] = null;
if (!pressedRight) keyTimers[CSInputs.RIGHT] = null;

// 移动方向:1前进 -1后退 0无
let moveDir = 0;
const forwardTime = keyTimers[CSInputs.FORWARD];
const backTime = keyTimers[CSInputs.BACK];
if (forwardTime !== null && backTime !== null) moveDir = forwardTime < backTime ? 1 : -1;
else if (forwardTime !== null) moveDir = 1;
else if (backTime !== null) moveDir = -1;

// 转向方向:1左转 -1右转 0无
let turnDir = 0;
const leftTime = keyTimers[CSInputs.LEFT];
const rightTime = keyTimers[CSInputs.RIGHT];
if (leftTime !== null && rightTime !== null) turnDir = leftTime < rightTime ? 1 : -1;
else if (leftTime !== null) turnDir = 1;
else if (rightTime !== null) turnDir = -1;

// 4. 获取自行车当前朝向
const boxAngles = bicycleBox.GetAbsAngles();
const pitch = boxAngles.pitch;
const roll = boxAngles.roll;

// 5. 获取当前速度和角速度
const currentVel = bicycleBox.GetAbsVelocity();
const currentAngVel = bicycleBox.GetAbsAngularVelocity();

// 6. 计算基础目标水平速度(基于玩家输入)
let baseTargetHor = { x: 0, y: 0 };
if (moveDir !== 0) {
const forward = getForwardVector(boxAngles);
const maxSpeed = moveDir > 0 ? MAX_SPEED_FWD : MAX_SPEED_REV;
baseTargetHor = { x: forward.x * maxSpeed * moveDir, y: forward.y * maxSpeed * moveDir };
}

// 7. 爬坡辅助(仅前进时且在角度范围内)
let hillAssistSpeed = 0;
if (moveDir > 0 && pitch >= HILL_ASSIST_ANGLE_MIN && pitch <= HILL_ASSIST_ANGLE_MAX) {
const factor = (pitch - HILL_ASSIST_ANGLE_MIN) / (HILL_ASSIST_ANGLE_MAX - HILL_ASSIST_ANGLE_MIN);
hillAssistSpeed = factor * HILL_ASSIST_MAX_SPEED;
const forward = getForwardVector(boxAngles);
baseTargetHor.x += forward.x * hillAssistSpeed;
baseTargetHor.y += forward.y * hillAssistSpeed;
}

// 8. 平滑加速/减速(水平方向)
const maxDelta = ACCELERATION * UPDATE_INTERVAL;
const newHor = applyAcceleration({ x: currentVel.x, y: currentVel.y }, baseTargetHor, maxDelta);

// 9. 合成新速度:保留垂直速度,加上爬坡辅助垂直分量
let newVelocity = { x: newHor.x, y: newHor.y, z: currentVel.z };
if (hillAssistSpeed > 0) {
const forward = getForwardVector(boxAngles);
newVelocity.z += forward.z * hillAssistSpeed;
}

// 10. 侧翻修正(基于滚转角)
let correctionAngVelY = 0;
if (Math.abs(roll) > ROLL_THRESHOLD) {
const error = -roll;
const k = 2.0;
correctionAngVelY = Math.max(-ROLL_CORRECTION_RATE, Math.min(ROLL_CORRECTION_RATE, error * k));
}

// 11. 新角速度:保留俯仰,修正滚转,设置偏航
const newAngularVelocity = {
x: currentAngVel.x,
y: correctionAngVelY,
z: turnDir * TURN_RATE
};

// 12. 应用 Teleport(只设置速度和角速度)
bicycleBox.Teleport({ velocity: newVelocity, angularVelocity: newAngularVelocity });

// 13. 安排下一次更新
Instance.SetNextThink(Instance.GetGameTime() + UPDATE_INTERVAL);
}

// 处理 Start 输入(上车)
Instance.OnScriptInput("start", (inputData) => {
const caller = inputData.caller;
const activator = inputData.activator;
if (!caller || !activator) return;

const suffix = getSuffixFromName(caller.GetEntityName());
if (!suffix) return;

const box = Instance.FindEntityByName(`bicycle_box_${suffix}`);
if (!box || !box.IsValid()) return;

if (!activator.IsAlive || !activator.IsAlive()) return;

if (isRiding) isRiding = false; // 替换旧骑手

bicycleBox = box;
riderPawn = activator;
isRiding = true;

if (!thinkRegistered) {
Instance.SetThink(think);
thinkRegistered = true;
}
Instance.SetNextThink(Instance.GetGameTime() + UPDATE_INTERVAL);
});

// 处理 Stop 输入(下车)
Instance.OnScriptInput("stop", () => {
isRiding = false;
bicycleBox = null;
riderPawn = null;
keyTimers = {};
});

// 处理脚本重载(Tools 模式)
Instance.OnScriptReload({
before: () => {
isRiding = false;
bicycleBox = null;
riderPawn = null;
thinkRegistered = false;
}
});
8 changes: 4 additions & 4 deletions 2001/ze_maontain_escape/countdown.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//纯预设模版的倒计时脚本,游戏内对point_script实体输入RunScriptInput,值为endin_30,则触发30秒内结束战斗的倒计时
//纯预设模版的倒计时脚本,游戏内对point_script实体输入RunScriptInput,值为pushin_30,则触发30秒内结束战斗的倒计时
//回合重启或者输入值为stop时倒计时会直接停止
//局内需存在以下实体
//①、一个game_zone_player固实体,命名为countdown_game_zone_player_hide,实体内io:OnPlayerOutZone>countdown_hudhint>HideHudHint>>0>-1
Expand All @@ -15,13 +15,13 @@ const CONFIG = {
ZONE_SHOW: "countdown_game_zone_player_show",
ZONE_HIDE: "countdown_game_zone_player_hide",
},
THINK_INTERVAL: 0.1,
THINK_INTERVAL: 0.1, // 更新间隔(秒)
ZERO_DISPLAY_TIME: 0.5 // 0 显示多久后消失(秒)
};

// 文本模板
const TEMPLATES = {
endin: "{time}秒内结束战斗",
pushin: "随机推力{time}",
flykill: "{time}秒后禁飞",
knifefight: "{time}秒后用刀决战!!!",
nukeboom: "{time}秒后核弹降临",
Expand Down Expand Up @@ -172,7 +172,7 @@ Instance.OnScriptInput("stop", CountdownManager.stop);

// 预设倒计时
[
"endin_30",
"pushin_5",
"flykill_30",
"knifefight_30",
"nukeboom_120",
Expand Down
132 changes: 132 additions & 0 deletions 2001/ze_maontain_escape/gt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { Instance, CSInputs } from "cs_script/point_script";

function vec(x, y, z) { return { x, y, z }; }
function add(v1, v2) { return { x: v1.x + v2.x, y: v1.y + v2.y, z: v1.z + v2.z }; }
function scale(v, s) { return { x: v.x * s, y: v.y * s, z: v.z * s }; }
function len3(v) { return Math.hypot(v.x, v.y, v.z); }
function fwd(a) {
const p = (a.pitch * Math.PI) / 180;
const y = (a.yaw * Math.PI) / 180;
const h = Math.cos(p);
return { x: Math.cos(y) * h, y: Math.sin(y) * h, z: -Math.sin(p) };
}
function right(a) {
const y = (a.yaw * Math.PI) / 180;
return { x: Math.cos(y + Math.PI/2), y: Math.sin(y + Math.PI/2), z: 0 };
}
function print(text) { Instance.Msg(text); }
function fireT(target, input, val, delay, caller, activator) {
Instance.EntFireAtTarget({ target, input, value: val, caller, activator, delay });
}

const CONFIG = {
MOVE: {
MAX_SPEED: 300,
ACCEL_TIME: 0.3,
DAMPING: 0.8,
DURATION: 10.0
},
THINK_DELAY: 0.1
};

const skillData = new Map(); // key: pawn, value: { endTime, velFwd, velRight }

function isSkillActive(pawn) {
const data = skillData.get(pawn);
return data && Instance.GetGameTime() < data.endTime;
}

function activateSkill(pawn) {
if (!pawn || !pawn.IsValid()) return false;
if (isSkillActive(pawn)) return false;

fireT(pawn, "keyvalues", "gravity 0", 0);
let vel = pawn.GetAbsVelocity();
vel.z = 200;
pawn.Teleport(undefined, undefined, vel);
fireT(pawn, "keyvalues", "gravity 1", CONFIG.MOVE.DURATION);

skillData.set(pawn, {
endTime: Instance.GetGameTime() + CONFIG.MOVE.DURATION,
velFwd: vec(0, 0, 0),
velRight: vec(0, 0, 0)
});
return true;
}

function deactivateSkill(pawn) {
if (skillData.has(pawn)) {
skillData.delete(pawn);
}
}

function updateMovement(pawn) {
const data = skillData.get(pawn);
if (!data) return;
const now = Instance.GetGameTime();
if (now >= data.endTime) {
deactivateSkill(pawn);
return;
}
if (!pawn.IsValid() || !pawn.IsAlive()) {
deactivateSkill(pawn);
return;
}

const angles = pawn.GetEyeAngles();
const forward = fwd(angles);
const rightVec = right(angles);
const delta = CONFIG.THINK_DELAY;
const maxSpeed = CONFIG.MOVE.MAX_SPEED;
const accel = maxSpeed / CONFIG.MOVE.ACCEL_TIME;

let fwdInput = 0;
if (pawn.IsInputPressed(CSInputs.FORWARD)) fwdInput = 1;
else if (pawn.IsInputPressed(CSInputs.BACK)) fwdInput = -1;

let rightInput = 0;
if (pawn.IsInputPressed(CSInputs.LEFT)) rightInput = 1;
else if (pawn.IsInputPressed(CSInputs.RIGHT)) rightInput = -1;

if (fwdInput !== 0) {
const accelVec = scale(forward, accel * delta * fwdInput);
data.velFwd = add(data.velFwd, accelVec);
const speed = len3(data.velFwd);
if (speed > maxSpeed) data.velFwd = scale(data.velFwd, maxSpeed / speed);
} else {
data.velFwd = scale(data.velFwd, CONFIG.MOVE.DAMPING);
if (len3(data.velFwd) < 0.01) data.velFwd = vec(0, 0, 0);
}

if (rightInput !== 0) {
const accelVec = scale(rightVec, accel * delta * rightInput);
data.velRight = add(data.velRight, accelVec);
const speed = len3(data.velRight);
if (speed > maxSpeed) data.velRight = scale(data.velRight, maxSpeed / speed);
} else {
data.velRight = scale(data.velRight, CONFIG.MOVE.DAMPING);
if (len3(data.velRight) < 0.01) data.velRight = vec(0, 0, 0);
}

const finalVel = add(data.velFwd, data.velRight);
pawn.Teleport(undefined, undefined, finalVel);
}

let lastThinkTime = 0;
function think() {
const now = Instance.GetGameTime();
lastThinkTime = now;
for (let [pawn] of skillData) updateMovement(pawn);
Instance.SetNextThink(now + CONFIG.THINK_DELAY);
}

Instance.SetThink(think);
Instance.SetNextThink(Instance.GetGameTime() + CONFIG.THINK_DELAY);

Instance.OnScriptInput("gt_shift", ({ activator }) => {
if (!activator || !activator.IsValid()) return;
let pawn = activator;
if (pawn.GetPlayerPawn) pawn = pawn.GetPlayerPawn();
if (!pawn || !pawn.IsValid()) return;
activateSkill(pawn);
});
Loading