Map name:
39 |Player position:
40 | 41 | 42 | 43 | 44 | 182 | 183 | 184 | 185 | 186 | "); -------------------------------------------------------------------------------- /scripts/vscripts/pages/mapname.nut: -------------------------------------------------------------------------------- 1 | ::fallbackGET <- function () {}; 2 | 3 | ::sendResponse("\"" + GetMapName() + "\"", "application/json"); 4 | -------------------------------------------------------------------------------- /scripts/vscripts/pages/slice.nut: -------------------------------------------------------------------------------- 1 | ::fallbackGET <- function () {}; 2 | 3 | local pos = pplayer.eyes.GetOrigin(); 4 | 5 | local fvec = pplayer.eyes.GetForwardVector().Normalize2D(); 6 | local uvec = Vector(0, 0, 1); 7 | 8 | ppmod.detach(function (args):(pos, fvec, uvec) { 9 | 10 | while (args.ang < PI) { 11 | 12 | local vec = (fvec * sin(args.ang) + uvec * cos(args.ang)) * 1024.0; 13 | local point = ppmod.ray(pos, pos + vec, "phys_bone_follower").point; 14 | 15 | args.points.push([ 16 | (pos - point).Length2D() / 1024.0, 17 | (point.z - pos.z) / 1024.0 18 | ]); 19 | 20 | args.ang += PI / 64.0; 21 | 22 | } 23 | 24 | local json = "[" + pplayer.eyes.GetAngles().x + ","; 25 | for (local i = 1; i < args.points.len(); i ++) { 26 | json += "[" + args.points[i][0] + "," + args.points[i][1] + "]"; 27 | if (i != args.points.len() - 1) json += ","; 28 | } 29 | json += "]"; 30 | 31 | ::sendResponse(json, "application/json"); 32 | 33 | }, { 34 | ang = 0, 35 | points = [] 36 | }); 37 | 38 | -------------------------------------------------------------------------------- /scripts/vscripts/pages/topdown.nut: -------------------------------------------------------------------------------- 1 | ::fallbackGET <- function () {}; 2 | 3 | const PIx2 = 6.28318; 4 | local pos = pplayer.eyes.GetOrigin(); 5 | local fvec = pplayer.eyes.GetForwardVector(); 6 | 7 | ppmod.detach(function (args):(pos, fvec) { 8 | 9 | while (args.ang < PIx2) { 10 | 11 | local vec = Vector(cos(args.ang), sin(args.ang)) * 2048; 12 | local frac = ppmod.ray(pos, pos + vec, "phys_bone_follower").fraction; 13 | 14 | args.points.push(vec * frac); 15 | 16 | args.ang += PIx2 / 96.0; 17 | 18 | } 19 | 20 | local json = "[[" + pos.x + "," + pos.y + "],["+ fvec.x +","+ fvec.y +"]"; 21 | foreach (point in args.points) { 22 | json += ",[" + point.x + "," + point.y + "]"; 23 | } 24 | json += "]"; 25 | 26 | ::sendResponse(json, "application/json"); 27 | 28 | }, { 29 | ang = 0, 30 | points = [] 31 | }); 32 | 33 | -------------------------------------------------------------------------------- /scripts/vscripts/ppmod4.nut: -------------------------------------------------------------------------------- 1 | /* 2 | ppmod version 4 3 | author: PortalRunner 4 | */ 5 | 6 | if (!("Entities" in this)) { 7 | printl("[ppmod] Error: ppmod4 was included in a scope without CEntities!"); 8 | return; 9 | } 10 | 11 | if ("ppmod" in this) { 12 | printl("[ppmod] Error: ppmod is already loaded!"); 13 | return; 14 | } 15 | 16 | ::ppmod <- {}; 17 | 18 | /********************/ 19 | // Global Utilities // 20 | /********************/ 21 | 22 | ::min <- function (a, b) return a > b ? b : a; 23 | ::max <- function (a, b) return a < b ? b : a; 24 | ::round <- function (a, b = 0) return floor (a * (b = pow(10, b)) + 0.5) / b; 25 | 26 | class pparray { 27 | 28 | constructor (size = 0, fill = null) { 29 | if (typeof size == "array") arr = size; 30 | else arr = array(size, fill); 31 | } 32 | 33 | function _typeof () return "array"; 34 | function _get (idx) return arr[idx]; 35 | function _set (idx, val) return arr[idx] = val; 36 | function _nexti (previdx) { 37 | if (this.len() == 0) return null; 38 | if (previdx == null) return 0; 39 | return previdx < this.len() - 1 ? previdx + 1 : null; 40 | } 41 | function _tostring () { 42 | local str = "["; 43 | for (local i = 0; i < arr.len(); i ++) { 44 | if (typeof arr[i] == "string") str += "\"" + arr[i] + "\""; 45 | else str += arr[i]; 46 | if (i != arr.len() - 1) str += ", "; 47 | } 48 | return str + "]"; 49 | } 50 | function _cmp (other) { 51 | for (local i = 0; i < min(arr.len(),other.len()); i ++) { 52 | if (arr[i] < other[i]) return -1; 53 | else if (arr[i] > other[i]) return 1; 54 | } 55 | if (arr.len() < other.len()) return -1; 56 | else if (arr.len() > other.len()) return 1; 57 | return 0; 58 | } 59 | 60 | function join (joinstr = "") { 61 | local str = ""; 62 | for (local i = 0; i < arr.len(); i ++) { 63 | str += arr[i]; 64 | if (i != arr.len() - 1) str += joinstr; 65 | } 66 | return str; 67 | } 68 | function len () return arr.len(); 69 | function append (val) return arr.append(val); 70 | function push (val) return arr.push(val); 71 | function extend (other) return arr.extend(other); 72 | function pop () return arr.pop(); 73 | function shift () return arr.remove(0); 74 | function unshift (val) return arr.insert(0, val); 75 | function top () return arr.top(); 76 | function insert (idx, val) return arr.insert(idx, val); 77 | function remove (idx) return arr.remove(idx); 78 | function resize (size, fill = null) return arr.resize(size, fill); 79 | function sort (func = null) return func ? arr.sort(func) : arr.sort(); 80 | function reverse () return arr.reverse(); 81 | function slice (start, end = null) return pparray(arr.slice(start, end || arr.len())); 82 | function tostring () return _tostring(); 83 | function clear () return arr.clear(); 84 | function equals (other) { 85 | if (arr.len() != other.len()) return 0; 86 | for (local i = 0; i < arr.len(); i ++) { 87 | if (typeof arr[i] == "array"){ 88 | if (arr[i].equals(other[i]) == 0) return 0; 89 | } else { 90 | if (arr[i] != other[i]) return 0; 91 | } 92 | } 93 | return 1; 94 | } 95 | function find (match) { 96 | if (typeof match == "function") { 97 | for (local i = 0; i < arr.len(); i ++) { 98 | if (match(arr[i])) return i; 99 | } 100 | return -1; 101 | } 102 | for (local i = 0; i < arr.len(); i ++) { 103 | if (arr[i] == match) return i; 104 | } 105 | return -1; 106 | } 107 | 108 | arr = null; 109 | 110 | } 111 | 112 | class ppheap { 113 | 114 | constructor (maxs = 0, comparator = null) { 115 | maxsize = maxs; 116 | arr = pparray(maxsize*4 + 1,0); 117 | if (comparator) { 118 | comp = comparator; 119 | } else { 120 | comp = function (a, b) { return a < b }; 121 | } 122 | } 123 | 124 | function isempty () { return size == 0 }; 125 | function bubbledown (hole) { 126 | local temp = arr[hole]; 127 | while (hole * 2 <= size) { 128 | local child = hole * 2; 129 | if (child != size && comp(arr[child + 1], arr[child])) child ++; 130 | if (comp(arr[child], temp)) { 131 | arr[hole] = arr[child] 132 | } else { 133 | break; 134 | } 135 | hole = child; 136 | } 137 | arr[hole] = temp; 138 | } 139 | function remove () { 140 | if (isempty()) { 141 | throw "Heap is empty"; 142 | } else { 143 | local tmp = arr[1]; 144 | arr[1] = arr[size--]; 145 | bubbledown(1); 146 | return tmp; 147 | } 148 | } 149 | function gettop () { 150 | if (isempty()) { 151 | throw "Heap is empty"; 152 | } else { 153 | return arr[1]; 154 | } 155 | } 156 | function insert (val) { 157 | if (size == maxsize) { 158 | throw "Exceeding max heap size"; 159 | } 160 | arr[0] = val; 161 | local hole = ++size; 162 | while (comp(val, arr[hole / 2])) { 163 | arr[hole] = arr[hole / 2]; 164 | hole /= 2; 165 | } 166 | arr[hole] = val; 167 | } 168 | 169 | arr = pparray([0]); 170 | size = 0; 171 | maxsize = 0; 172 | comp = null; 173 | 174 | } 175 | 176 | class ppstring { 177 | 178 | constructor (str = "") { 179 | string = str.tostring(); 180 | } 181 | 182 | function _typeof () return "string"; 183 | function _tostring () return string; 184 | function _add (other) return ppstring(string + other.tostring()); 185 | function _get (idx) return string[idx]; 186 | function _set (idx, val) return string = string.slice(0, idx) + val.tochar() + string.slice(idx + 1); 187 | function _cmp (other) { 188 | if (string == other.tostring()) return 0; 189 | if (string > other.tostring()) return 1; 190 | return -1; 191 | } 192 | 193 | function len () return string.len(); 194 | function tointeger () return string.tointeger(); 195 | function tofloat () return string.tofloat(); 196 | function tostring () return string; 197 | function slice (start, end = null) return ppstring(string.slice(start, end || string.len())); 198 | function find (substr, startidx = 0) return string.find(substr, startidx); 199 | function tolower () return ppstring(string.tolower()); 200 | function toupper () return ppstring(string.toupper()); 201 | function split (substr) { 202 | local arr = [], curr = 0, prev = 0; 203 | while ((curr = string.find(substr, curr)) != null) { 204 | curr = max(curr, prev + 1); 205 | arr.push(string.slice(prev, curr)); 206 | prev = curr += substr.len(); 207 | } 208 | arr.push(string.slice(prev)); 209 | return arr; 210 | } 211 | function strip () return ppstring(::strip(string)); 212 | function lstrip () return ppstring(::lstrip(string)); 213 | function rstrip () return ppstring(::rstrip(string)); 214 | function replace (substr, rep) return ppstring(pparray(this.split(substr)).join(rep)); 215 | 216 | string = null; 217 | 218 | } 219 | 220 | local ppromise_base = { 221 | 222 | then = function (onthen = null, oncatch = null) { 223 | 224 | if (typeof onthen != "function") onthen = identity; 225 | if (typeof oncatch != "function") oncatch = thrower; 226 | 227 | if (state == "fulfilled") { onthen(value); return this } 228 | if (state == "rejected") { oncatch(value); return this } 229 | 230 | onfulfill.push(onthen); 231 | onreject.push(oncatch); 232 | 233 | return this; 234 | 235 | }, 236 | 237 | except = function (oncatch = null) { 238 | 239 | if (typeof oncatch != "function") oncatch = thrower; 240 | 241 | if (state == "rejected") return oncatch(value); 242 | onreject.push(oncatch); 243 | 244 | return this; 245 | 246 | }, 247 | 248 | finally = function (onfinally) { 249 | 250 | if (state != "pending") return onfinally(value); 251 | onresolve.push(onfinally); 252 | 253 | return this; 254 | 255 | } 256 | 257 | } 258 | 259 | ::ppromise <- function (func):(ppromise_base) { 260 | 261 | local inst = { 262 | 263 | onresolve = [], 264 | onfulfill = [], 265 | onreject = [], 266 | 267 | state = "pending", 268 | value = null, 269 | 270 | identity = function (x) { return x }, 271 | thrower = function (x) { throw x }, 272 | 273 | then = ppromise_base.then, 274 | except = ppromise_base.except, 275 | finally = ppromise_base.finally 276 | 277 | resolve = null, 278 | reject = null 279 | 280 | }; 281 | 282 | inst.resolve = function (val = null):(inst) { 283 | 284 | if (inst.state != "pending") return; 285 | 286 | inst.state = "fulfilled"; 287 | inst.value = val; 288 | 289 | for (local i = 0; i < inst.onfulfill.len(); i ++) inst.onfulfill[i](val); 290 | for (local i = 0; i < inst.onresolve.len(); i ++) inst.onresolve[i](); 291 | 292 | }; 293 | 294 | inst.reject = function (err = null):(inst) { 295 | 296 | if (inst.state != "pending") return; 297 | 298 | inst.state = "rejected"; 299 | inst.value = err; 300 | 301 | if (inst.onreject.len() == 0) inst.thrower(err); 302 | else for (local i = 0; i < inst.onreject.len(); i ++) inst.onreject[i](err); 303 | for (local i = 0; i < inst.onresolve.len(); i ++) inst.onresolve[i](); 304 | 305 | }; 306 | 307 | try { 308 | func(inst.resolve, inst.reject); 309 | } catch (e) { 310 | inst.reject(e); 311 | } 312 | 313 | return inst; 314 | 315 | } 316 | 317 | ::ppmod.asyncgen <- []; 318 | ::ppmod.asyncrun <- function (id, resolve, reject) { 319 | 320 | local next; 321 | try { 322 | next = resume ppmod.asyncgen[id]; 323 | } catch (e) { 324 | return reject(e); 325 | } 326 | 327 | if (ppmod.asyncgen[id].getstatus() == "dead") { 328 | ppmod.asyncgen[id] = null; 329 | return resolve(next); 330 | } 331 | 332 | next.then(function (val):(id, resolve, reject) { 333 | ::yielded <- val; 334 | ppmod.asyncrun(id, resolve, reject); 335 | }); 336 | 337 | } 338 | 339 | ::yielded <- null; 340 | ::async <- function (func) { 341 | 342 | return function (...):(func) { 343 | 344 | local args = array(vargc + 1); 345 | for (local i = 0; i < vargc; i ++) args[i + 1] = vargv[i]; 346 | args[0] = this; 347 | 348 | return ppromise(function (resolve, reject):(func, args) { 349 | 350 | for (local i = 0; i < ppmod.asyncgen.len(); i ++) { 351 | if (ppmod.asyncgen[i] == null) { 352 | ppmod.asyncgen[i] = func.acall(args); 353 | ppmod.asyncrun(i, resolve, reject); 354 | return; 355 | } 356 | } 357 | 358 | ppmod.asyncgen.push(func.acall(args)); 359 | ppmod.asyncrun(ppmod.asyncgen.len() - 1, resolve, reject); 360 | 361 | }); 362 | 363 | } 364 | 365 | } 366 | 367 | try { 368 | 369 | function Vector::_mul (other) { 370 | if (typeof other == "Vector") { 371 | return Vector(this.x * other.x, this.y * other.y, this.z * other.z); 372 | } else { 373 | return Vector(this.x * other, this.y * other, this.z * other); 374 | } 375 | } 376 | 377 | function Vector::_div (other) { 378 | if (typeof other == "Vector") { 379 | return Vector(this.x / other.x, this.y / other.y, this.z / other.z); 380 | } else { 381 | return Vector(this.x / other, this.y / other, this.z / other); 382 | } 383 | } 384 | 385 | function Vector::_unm () { 386 | return Vector() - this; 387 | } 388 | 389 | function Vector::equals (other) { 390 | if (this.x == other.x && this.y == other.y && this.z == other.z) return true; 391 | return false; 392 | } 393 | 394 | function Vector::_tostring () { 395 | return "Vector(" + this.x + ", " + this.y + ", " + this.z + ")"; 396 | } 397 | 398 | function Vector::ToKVString () { 399 | return this.x + " " + this.y + " " + this.z; 400 | } 401 | 402 | function Vector::Normalize() { 403 | this.Norm(); 404 | return this; 405 | } 406 | 407 | function Vector::Normalize2D() { 408 | this.z = 0.0; 409 | this.Norm(); 410 | return this; 411 | } 412 | 413 | } catch (e) { 414 | 415 | printl("[ppmod] Warning: failed to modify Vector class: " + e); 416 | 417 | } 418 | 419 | /*********************/ 420 | // Entity management // 421 | /*********************/ 422 | 423 | ::ppmod.get <- function (arg1, arg2 = null, arg3 = null, arg4 = null) { 424 | 425 | local curr = null; 426 | 427 | if (typeof arg1 == "string") { 428 | 429 | if (curr = Entities.FindByName(arg2, arg1)) return curr; 430 | if (curr = Entities.FindByClassname(arg2, arg1)) return curr; 431 | return Entities.FindByModel(arg2, arg1); 432 | 433 | } 434 | 435 | if (typeof arg1 == "Vector") { 436 | 437 | if (arg2 == null) arg2 = 32.0; 438 | 439 | if (typeof arg3 == "string") { 440 | local filter = arg3; 441 | arg3 = arg4; 442 | arg4 = filter; 443 | } 444 | 445 | if (typeof arg4 == "string") { 446 | 447 | while (arg3 = Entities.FindInSphere(arg3, arg1, arg2)) { 448 | if (!arg3.IsValid()) continue; 449 | if (arg3.GetName() == arg4 || arg3.GetClassname() == arg4 || arg3.GetModelName() == arg4) { 450 | return arg3; 451 | } 452 | } 453 | 454 | return null; 455 | 456 | } else { 457 | return Entities.FindInSphere(arg3, arg1, arg2); 458 | } 459 | 460 | } 461 | 462 | if (typeof arg1 == "integer") { 463 | 464 | while (curr = Entities.Next(curr)) { 465 | if (!curr.IsValid()) continue; 466 | if (curr.entindex() == arg1) return curr; 467 | } 468 | 469 | return null; 470 | 471 | } 472 | 473 | if (typeof arg1 == "instance" && arg1 instanceof CBaseEntity) { 474 | return ent; 475 | } 476 | return null; 477 | 478 | } 479 | 480 | ::ppmod.getall <- function (args, callback) { 481 | 482 | if (typeof args != "array") { 483 | args = [args]; 484 | } 485 | args.push(null); 486 | args.insert(0, this); 487 | 488 | local curr = null; 489 | 490 | while (true) { 491 | 492 | curr = ppmod.get.acall(args); 493 | 494 | if (!curr) return; 495 | callback(curr); 496 | 497 | args[args.len() - 1] = curr; 498 | 499 | } 500 | 501 | } 502 | 503 | ::ppmod.prev <- function (...) { 504 | 505 | local start = null, curr = null, prev = null; 506 | 507 | if (vargc > 1) { 508 | start = vargv[vargc - 1]; 509 | curr = start; 510 | } 511 | 512 | while (true) { 513 | 514 | if (vargc < 3) curr = ppmod.get(vargv[0], curr); 515 | else if (vargc == 3) curr = ppmod.get(vargv[0], vargv[1], curr); 516 | else curr = ppmod.get(vargv[0], vargv[1], vargv[2], curr); 517 | 518 | if (curr == start) return prev; 519 | prev = curr; 520 | 521 | } 522 | 523 | } 524 | 525 | ::ppmod.fire <- function (ent, action = "Use", value = "", delay = 0.0, activator = null, caller = null) { 526 | 527 | if (typeof ent == "string") { 528 | DoEntFire(ent, action, value.tostring(), delay, activator, caller); 529 | return; 530 | } 531 | 532 | if (!(typeof ent == "instance" && ent instanceof CBaseEntity)) { 533 | ppmod.getall(ent, function (curr):(action, value, delay, activator, caller) { 534 | ppmod.fire(curr, action, value, delay, activator, caller); 535 | }); 536 | return; 537 | } 538 | 539 | EntFireByHandle(ent, action, value.tostring(), delay, activator, caller); 540 | 541 | } 542 | 543 | ::ppmod.keyval <- function (ent, key, val) { 544 | 545 | if (!(typeof ent == "instance" && ent instanceof CBaseEntity)) { 546 | ppmod.getall(ent, function (curr):(key, val) { 547 | ppmod.keyval(curr, key, val); 548 | }); 549 | return; 550 | } 551 | 552 | switch (typeof val) { 553 | 554 | case "integer": 555 | case "bool": 556 | ent.__KeyValueFromInt(key, val.tointeger()); 557 | break; 558 | case "float": 559 | ent.__KeyValueFromFloat(key, val); 560 | break; 561 | case "Vector": 562 | ent.__KeyValueFromVector(key, val); 563 | break; 564 | default: 565 | ent.__KeyValueFromString(key, val.tostring()); 566 | 567 | } 568 | 569 | } 570 | 571 | ::ppmod.flags <- function (ent, ...) { 572 | 573 | local sum = 0; 574 | for (local i = 0; i < vargc; i ++) { 575 | sum += vargv[i]; 576 | } 577 | 578 | ppmod.keyval(ent, "SpawnFlags", sum); 579 | 580 | } 581 | 582 | ::ppmod.addoutput <- function (ent, output, target, input = "Use", value = "", delay = 0, max = -1) { 583 | 584 | if (typeof target != "string") { 585 | 586 | ppmod.addscript(ent, output, function ():(target, input, value) { 587 | ppmod.fire(target, input, value, 0.0, activator, caller); 588 | }, delay, max, false); 589 | 590 | return; 591 | 592 | } 593 | 594 | ppmod.keyval(ent, output, target+"\x1B"+input+"\x1B"+value+"\x1B"+delay+"\x1B"+max); 595 | 596 | } 597 | 598 | ::ppmod.scrq <- []; 599 | 600 | ::ppmod.scrq_add <- function (scr, max = -1) { 601 | 602 | if (typeof scr == "string") { 603 | scr = compilestring(scr); 604 | } 605 | 606 | for (local i = 0; i < ppmod.scrq.len(); i ++) { 607 | if (!ppmod.scrq[i]) { 608 | ppmod.scrq[i] = [scr, max]; 609 | return i; 610 | } 611 | } 612 | 613 | ppmod.scrq.push([scr, max]); 614 | return ppmod.scrq.len() - 1; 615 | 616 | } 617 | 618 | ::ppmod.scrq_get <- function (idx) { 619 | 620 | local scr = ppmod.scrq[idx][0]; 621 | 622 | if (ppmod.scrq[idx][1] > 0 && --ppmod.scrq[idx][1] == 0) { 623 | ppmod.scrq[idx] = null; 624 | } 625 | 626 | return scr; 627 | 628 | } 629 | 630 | ::ppmod.addscript <- function (ent, output, scr = "", delay = 0, max = -1, passthrough = false) { 631 | 632 | if (typeof scr == "function") { 633 | if (passthrough) scr = "ppmod.scrq_get(" + ppmod.scrq_add(scr, max) + ")(activator, caller)"; 634 | else scr = "ppmod.scrq_get(" + ppmod.scrq_add(scr, max) + ")()"; 635 | } 636 | 637 | ppmod.keyval(ent, output, "worldspawn\x001BRunScriptCode\x1B"+scr+"\x1B"+delay+"\x1B"+max); 638 | 639 | } 640 | 641 | ::ppmod.runscript <- function (ent, scr) { 642 | 643 | if (typeof scr == "function") { 644 | scr = "ppmod.scrq_get(" + ppmod.scrq_add(scr, 1) + ")()"; 645 | } 646 | 647 | ppmod.fire(ent, "RunScriptCode", scr); 648 | 649 | } 650 | 651 | ::ppmod.setparent <- function (child, _parent) { 652 | 653 | if (_parent) ppmod.fire(child, "SetParent", "!activator", 0, _parent); 654 | else ppmod.fire(child, "ClearParent"); 655 | 656 | } 657 | 658 | ::ppmod.hook <- function (ent, input, scr, max = -1) { 659 | 660 | if (!(typeof ent == "instance" && ent instanceof CBaseEntity)) { 661 | ppmod.getall(ent, function (curr):(input, scr, max) { 662 | ppmod.hook(curr, input, scr, max); 663 | }); 664 | return; 665 | } 666 | 667 | if (!ent.ValidateScriptScope()) { 668 | throw "ppmod.hook: Could not validate entity script scope"; 669 | } 670 | 671 | if (scr == null) ent.GetScriptScope()["Input"+input] <- function () return true; 672 | else ent.GetScriptScope()["Input"+input] <- ppmod.scrq_get(ppmod.scrq_add(scr, max)); 673 | 674 | } 675 | 676 | // Implement shorthands of the above functions into the entities as methods 677 | local entclasses = [CBaseEntity, CBaseAnimating, CBaseFlex, CBasePlayer, CEnvEntityMaker, CLinkedPortalDoor, CPortal_Player, CPropLinkedPortalDoor, CSceneEntity, CTriggerCamera]; 678 | for (local i = 0; i < entclasses.len(); i ++) { 679 | 680 | try { 681 | 682 | entclasses[i]._set <- function (key, val) { 683 | switch (typeof val) { 684 | case "integer": 685 | case "bool": 686 | this.__KeyValueFromInt(key, val.tointeger()); 687 | break; 688 | case "float": 689 | this.__KeyValueFromFloat(key, val); 690 | break; 691 | case "Vector": 692 | this.__KeyValueFromVector(key, val); 693 | break; 694 | default: 695 | this.__KeyValueFromString(key, val.tostring()); 696 | } 697 | return val; 698 | } 699 | entclasses[i]._get <- function (key) { 700 | return function (value = "", delay = 0.0, activator = null, caller = null):(key) { 701 | return ::EntFireByHandle(this, key, value.tostring(), delay, activator, caller); 702 | } 703 | } 704 | 705 | entclasses[i].Fire <- function (action = "Use", value = "", delay = 0.0, activator = null, caller = null) { 706 | return ::EntFireByHandle(this, action, value.tostring(), delay, activator, caller); 707 | } 708 | entclasses[i].AddOutput <- function (output, target, input = "Use", value = "", delay = 0, max = -1) { 709 | return ::ppmod.addoutput(this, output, target, input, value, delay, max); 710 | } 711 | entclasses[i].AddScript <- function (output, scr = "", delay = 0, max = -1, passthrough = false) { 712 | return ::ppmod.addscript(this, output, scr, delay, max, passthrough); 713 | } 714 | entclasses[i].RunScript <- function (scr) { 715 | return ::ppmod.runscript(this, scr); 716 | } 717 | entclasses[i].SetMoveParent <- function (_parent) { 718 | return ::ppmod.setparent(this, _parent); 719 | } 720 | entclasses[i].SetHook <- function (input, scr, max = -1) { 721 | return ::ppmod.hook(this, input, scr, max); 722 | } 723 | 724 | } catch (e) { 725 | 726 | local classname; 727 | switch (entclasses[i]) { 728 | case CBaseEntity: classname = "CBaseEntity"; break; 729 | case CBaseAnimating: classname = "CBaseAnimating"; break; 730 | case CBaseFlex: classname = "CBaseFlex"; break; 731 | case CBasePlayer: classname = "CBasePlayer"; break; 732 | case CEnvEntityMaker: classname = "CEnvEntityMaker"; break; 733 | case CLinkedPortalDoor: classname = "CLinkedPortalDoor"; break; 734 | case CPortal_Player: classname = "CPortal_Player"; break; 735 | case CPropLinkedPortalDoor: classname = "CPropLinkedPortalDoor"; break; 736 | case CSceneEntity: classname = "CSceneEntity"; break; 737 | case CTriggerCamera: classname = "CTriggerCamera"; break; 738 | } 739 | 740 | printl("[ppmod] Warning: failed to modify " + classname + " class: " + e); 741 | 742 | } 743 | 744 | } 745 | 746 | /****************/ 747 | // Control flow // 748 | /****************/ 749 | 750 | ::ppmod.wait <- function (scr, sec, name = "") { 751 | 752 | local relay = Entities.CreateByClassname("logic_relay"); 753 | relay.__KeyValueFromString("Targetname", name); 754 | 755 | ppmod.addscript(relay, "OnTrigger", scr, 0, 1); 756 | EntFireByHandle(relay, "Trigger", "", sec, null, null); 757 | relay.__KeyValueFromInt("SpawnFlags", 1); 758 | 759 | return relay; 760 | 761 | } 762 | 763 | ::ppmod.interval <- function (scr, sec = 0.0, name = "") { 764 | 765 | local timer = Entities.CreateByClassname("logic_timer"); 766 | timer.__KeyValueFromString("Targetname", name); 767 | 768 | ppmod.addscript(timer, "OnTimer", scr); 769 | EntFireByHandle(timer, "RefireTime", sec.tostring(), 0.0, null, null); 770 | EntFireByHandle(timer, "Enable", "", 0.0, null, null); 771 | 772 | return timer; 773 | 774 | } 775 | 776 | ::ppmod.ontick <- function (scr, pause = true, timeout = -1) { 777 | 778 | if (typeof scr == "function") { 779 | if (timeout == -1) scr = "ppmod.scrq_get(" + ppmod.scrq_add(scr, -1) + ")()"; 780 | else scr = "ppmod.scrq_get(" + ppmod.scrq_add(scr, 1) + ")()"; 781 | } 782 | 783 | if (pause && FrameTime() == 0.0) { 784 | SendToConsole("script ppmod.ontick(\"" + scr + "\", true, " + timeout + ")"); 785 | return; 786 | } 787 | 788 | if (timeout == -1) { 789 | SendToConsole("script " + scr + ";script ppmod.ontick(\"" + scr + "\", " + pause + ")"); 790 | return; 791 | } 792 | 793 | if (timeout == 0) SendToConsole("script " + scr); 794 | else SendToConsole("script ppmod.ontick(\"" + scr + "\", " + pause + ", " + (timeout - 1) + ")"); 795 | 796 | } 797 | 798 | ::ppmod.once <- function (scr, name = null) { 799 | 800 | if (!name) name = scr.tostring(); 801 | if (Entities.FindByName(null, name)) return; 802 | 803 | local relay = Entities.CreateByClassname("logic_relay"); 804 | relay.__KeyValueFromString("Targetname", name); 805 | 806 | ppmod.addscript(relay, "OnTrigger", scr, 0, 1); 807 | EntFireByHandle(relay, "Trigger", "", 0.0, null, null); 808 | 809 | return relay; 810 | 811 | } 812 | 813 | ::ppmod.onauto <- function (scr, onload = false) { 814 | 815 | local auto = Entities.CreateByClassname("logic_auto"); 816 | 817 | // In online multiplayer games, we delay spawning until both players are ready 818 | if (IsMultiplayer()) scr = function ():(scr) { 819 | 820 | local outerinterval = UniqueString("ppmod_auto_outerinterval"); 821 | 822 | ppmod.interval(function ():(scr, outerinterval) { 823 | 824 | // Find the host player, typically the first player named "blue" 825 | local blue = Entities.FindByName(null, "blue"); 826 | if (!blue || !blue.IsValid() || blue.GetClassname() != "player") { 827 | blue = Entities.FindByClassname(null, "player"); 828 | } 829 | if (!blue || !blue.IsValid()) return; 830 | 831 | Entities.FindByName(null, outerinterval).Destroy(); 832 | 833 | if (IsLocalSplitScreen()) { 834 | if (typeof scr == "string") compilestring(scr)(); 835 | else scr(); 836 | return; 837 | } 838 | 839 | // Find the lowest significant point of the world's bounding box estimate 840 | local ent = null, lowest = 0, curr; 841 | while (ent = Entities.Next(ent)) { 842 | 843 | if (!ent.IsValid()) continue; 844 | 845 | curr = ent.GetOrigin().z + ent.GetBoundingMins().z; 846 | if (curr < lowest) lowest = curr; 847 | 848 | } 849 | 850 | // Additional decrement just to make sure we're below anything significant 851 | lowest -= 1024.0; 852 | 853 | // We move the host below the map and wait until they are teleported back up 854 | // This happens once both players finish connecting in network games 855 | blue.SetOrigin(Vector(0, 0, lowest)); 856 | 857 | local intervalname = UniqueString("ppmod_auto_interval"); 858 | ppmod.interval(function ():(blue, lowest, scr, intervalname) { 859 | 860 | local red = Entities.FindByClassname(null, "red"); 861 | if (!red || !red.IsValid() || red.GetClassname() != "player") { 862 | red = Entities.FindByClassname(blue, "player"); 863 | } 864 | 865 | if (!red || !red.IsValid() || blue.GetOrigin().z <= lowest) return; 866 | 867 | if (typeof scr == "string") compilestring(scr)(); 868 | else scr(); 869 | 870 | Entities.FindByName(null, intervalname).Destroy(); 871 | 872 | }, 0.0, intervalname); 873 | 874 | }, 0.0, outerinterval); 875 | 876 | }; 877 | 878 | ppmod.addscript(auto, "OnNewGame", scr); 879 | ppmod.addscript(auto, "OnMapTransition", scr); 880 | if (onload) ppmod.addscript(auto, "OnLoadGame", scr); 881 | 882 | return auto; 883 | 884 | } 885 | 886 | ::ppmod.detach <- function (scr, args, stack = null) { 887 | 888 | if (stack == null) stack = getstackinfos(2); 889 | 890 | try { 891 | 892 | scr(args); 893 | 894 | } catch (e) { 895 | 896 | if (e == "Script terminated by SQQuerySuspend") { 897 | ppmod.detach(scr, args, stack); 898 | return; 899 | } 900 | 901 | printl("\nAN ERROR HAS OCCURED [" + e + "]"); 902 | printl("Caught within ppmod.detach in file " + stack.src + " on line " + stack.line + "\n"); 903 | 904 | } 905 | 906 | } 907 | 908 | /********************/ 909 | // Player interface // 910 | /********************/ 911 | 912 | ::ppmod.player <- function (player) { 913 | 914 | local pplayer = {}; 915 | pplayer.ent <- player; 916 | 917 | // One logic_playerproxy is required for registering jumping and ducking 918 | local proxy = Entities.FindByClassname(null, "logic_playerproxy"); 919 | if (!proxy) proxy = Entities.CreateByClassname("logic_playerproxy"); 920 | 921 | // Set up a logic_measure_movement for more accurate view angles 922 | pplayer.eyes <- Entities.CreateByClassname("logic_measure_movement"); 923 | local eyename = UniqueString("ppmod_eyes"); 924 | 925 | pplayer.eyes.__KeyValueFromInt("MeasureType", 1); 926 | pplayer.eyes.__KeyValueFromString("Targetname", eyename); 927 | pplayer.eyes.__KeyValueFromString("TargetReference", eyename); 928 | pplayer.eyes.__KeyValueFromString("Target", eyename); 929 | pplayer.eyes.SetAngles(0, 0, 90.0); 930 | 931 | EntFireByHandle(pplayer.eyes, "SetMeasureReference", eyename, 0.0, null, null); 932 | EntFireByHandle(pplayer.eyes, "Enable", "", 0.0, null, null); 933 | 934 | // logic_measure_movement relies on targetname for selecting entities 935 | // This changes the player's targetname briefly and set it back right away 936 | local nameswap = function ():(pplayer) { 937 | 938 | local playername = pplayer.ent.GetName(); 939 | pplayer.ent.__KeyValueFromString("Targetname", UniqueString("pplayer")); 940 | 941 | EntFireByHandle(pplayer.eyes, "SetMeasureTarget", pplayer.ent.GetName(), 0.0, null, null); 942 | 943 | ppmod.wait(function ():(pplayer, playername) { 944 | pplayer.ent.__KeyValueFromString("Targetname", playername); 945 | }, FrameTime()); 946 | 947 | }; 948 | 949 | nameswap(); // Swap the player's name now, and on every next load 950 | local auto = Entities.CreateByClassname("logic_auto"); 951 | auto.__KeyValueFromString("OnMapSpawn", "!self\x001BRunScriptCode\x001Bppmod.scrq_get(" + ppmod.scrq_add(nameswap, -1) + ")()\x001B0\x001B-1"); 952 | 953 | // Set up a game_ui for listening to player movement inputs 954 | local gameui = Entities.CreateByClassname("game_ui"); 955 | gameui.__KeyValueFromInt("FieldOfView", -1); 956 | EntFireByHandle(gameui, "Activate", "", 0.0, player, null); 957 | 958 | // Used later for attaching outputs to the player landing after a fall 959 | local landrl = Entities.CreateByClassname("logic_relay"); 960 | 961 | // Some unique values are saved in the player's script scope 962 | if (player.ValidateScriptScope()) { 963 | 964 | // Set up a simple loop for watching if the player is grounded 965 | local vel = player.GetVelocity().z; 966 | 967 | player.GetScriptScope()["ppmod_player_prevZ"] <- vel; 968 | player.GetScriptScope()["ppmod_player_grounded"] <- vel == 0; 969 | 970 | ppmod.interval(function ():(player, landrl) { 971 | 972 | local vel = player.GetVelocity().z; 973 | 974 | local prev = player.GetScriptScope()["ppmod_player_prevZ"]; 975 | local grounded = player.GetScriptScope()["ppmod_player_grounded"]; 976 | 977 | if (prev != 0 && vel != 0) grounded = false; 978 | if (prev <= 0 && vel == 0 && !grounded) { 979 | grounded = true; 980 | EntFireByHandle(landrl, "Trigger", "", 0.0, null, null); 981 | } 982 | 983 | player.GetScriptScope()["ppmod_player_prevZ"] <- vel; 984 | player.GetScriptScope()["ppmod_player_grounded"] <- grounded; 985 | 986 | }); 987 | 988 | // Set up a trigger_gravity for modifying the player's local gravity 989 | ppmod.trigger(player.GetOrigin() + Vector(0, 0, 36.5), Vector(16, 16, 36), "trigger_gravity", Vector(), true).then(function (trigger):(player) { 990 | 991 | trigger.__KeyValueFromFloat("Gravity", 1.0); 992 | EntFireByHandle(trigger, "Disable", "", 0.0, null, null); 993 | player.GetScriptScope()["ppmod_player_gravity"] <- trigger; 994 | 995 | ppmod.interval(function ():(trigger, player) { 996 | trigger.SetAbsOrigin(player.GetCenter()); 997 | }); 998 | 999 | }); 1000 | 1001 | } 1002 | 1003 | pplayer.holding <- function (classes = null):(player) { 1004 | 1005 | // List of all known holdable entities 1006 | if (classes == null) { 1007 | classes = pparray([ 1008 | "prop_weighted_cube", 1009 | "prop_monster_box", 1010 | "prop_physics", 1011 | "prop_physics_override", 1012 | "prop_physics_paintable", 1013 | "npc_personality_core", 1014 | "npc_portal_turret_floor", 1015 | "npc_security_camera", 1016 | "prop_glass_futbol" 1017 | ]); 1018 | } else if (!(classes instanceof pparray)) { 1019 | classes = pparray(classes); 1020 | } 1021 | 1022 | return ppromise(function (resolve, reject):(classes) { 1023 | 1024 | local scrqid = ppmod.scrq_add(resolve, 1); 1025 | local name = UniqueString("ppmod_holding"); 1026 | 1027 | local filter = Entities.CreateByClassname("filter_player_held"); 1028 | filter.__KeyValueFromString("Targetname", name); 1029 | filter.__KeyValueFromString("OnPass", "!self\x001BRunScriptCode\x001Bppmod.scrq_get(" + scrqid + ")(true);self.Destroy()\x001B0\x001B1"); 1030 | 1031 | local relay = Entities.CreateByClassname("logic_relay"); 1032 | relay.__KeyValueFromString("OnUser1", name + "\x001BRunScriptCode\x001Bppmod.scrq_get(" + scrqid + ")(false);self.Destroy()\x001B0\x001B1"); 1033 | relay.__KeyValueFromString("OnUser1", "!self\x001BOnUser2\x1B\x001B0\x001B1"); 1034 | relay.__KeyValueFromString("OnUser2", "!self\x001BKill\x1B\x001B0\x001B1"); 1035 | 1036 | // Due to filter_player_held not differentiating between players, 1037 | // Co-op checks only a 128u radius from the player, assuming VM grab controller 1038 | if (IsMultiplayer()) { 1039 | 1040 | local curr = null; 1041 | while (curr = Entities.FindInSphere(curr, player.GetCenter(), 128.0)) { 1042 | 1043 | if (!curr.IsValid()) continue; 1044 | if (classes.find(curr.GetClassname()) == -1) continue; 1045 | 1046 | EntFireByHandle(filter, "TestActivator", "", 0.0, curr, null); 1047 | 1048 | } 1049 | 1050 | } else { 1051 | 1052 | local curr = null; 1053 | while (curr = Entities.Next(curr)) { 1054 | 1055 | if (!curr.IsValid()) continue; 1056 | if (classes.find(curr.GetClassname()) == -1) continue; 1057 | 1058 | EntFireByHandle(filter, "TestActivator", "", 0.0, curr, null); 1059 | 1060 | } 1061 | 1062 | } 1063 | 1064 | EntFireByHandle(relay, "FireUser1", "", 0.0, null, null); 1065 | EntFireByHandle(relay, "Kill", "", 0.0, null, null); 1066 | 1067 | }); 1068 | 1069 | }; 1070 | 1071 | pplayer.jump <- function (scr):(player, proxy) { 1072 | local scrqstr = "ppmod.scrq_get(" + ppmod.scrq_add(scr) + ")()"; 1073 | ppmod.addoutput(proxy, "OnJump", player, "RunScriptCode", "if (self == activator) " + scrqstr); 1074 | }; 1075 | 1076 | pplayer.land <- function (scr):(landrl) { 1077 | ppmod.addscript(landrl, "OnTrigger", scr); 1078 | }; 1079 | pplayer.grounded <- function ():(player) { 1080 | return player.GetScriptScope()["ppmod_player_grounded"]; 1081 | }; 1082 | 1083 | pplayer.duck <- function (scr):(player, proxy) { 1084 | local scrqstr = "ppmod.scrq_get(" + ppmod.scrq_add(scr) + ")()"; 1085 | ppmod.addoutput(proxy, "OnDuck", player, "RunScriptCode", "if (self == activator) " + scrqstr); 1086 | }; 1087 | 1088 | pplayer.unduck <- function (scr):(player, proxy) { 1089 | local scrqstr = "ppmod.scrq_get(" + ppmod.scrq_add(scr) + ")()"; 1090 | ppmod.addoutput(proxy, "OnUnDuck", player, "RunScriptCode", "if (self == activator) " + scrqstr); 1091 | }; 1092 | 1093 | pplayer.ducking <- function ():(player) { 1094 | return player.GetCenter().z - player.GetOrigin().z < 18.001; 1095 | }; 1096 | 1097 | pplayer.input <- function (str, scr):(gameui) { 1098 | if (str[0] == '+') str = "pressed" + str.slice(1); 1099 | else str = "unpressed" + str.slice(1); 1100 | ppmod.addscript(gameui, str, scr); 1101 | }; 1102 | 1103 | pplayer.gravity <- function (gravity):(player) { 1104 | local trigger = player.GetScriptScope()["ppmod_player_gravity"]; 1105 | if (gravity == 1.0) EntFireByHandle(trigger, "Disable", "", 0.0, null, null); 1106 | else EntFireByHandle(trigger, "Enable", "", 0.0, null, null); 1107 | // Zero values have no effect, this is hacky but works well enough 1108 | if (gravity == 0.0) trigger.__KeyValueFromString("Gravity", "0.0000000000000001"); 1109 | else trigger.__KeyValueFromFloat("Gravity", gravity); 1110 | }; 1111 | 1112 | pplayer.friction <- function (fric, ftime = null, grounded = null):(pplayer) { 1113 | 1114 | // Don't touch velocity if the player isn't grounded 1115 | if (grounded == false) return; 1116 | if (grounded == null && !pplayer.grounded()) return; 1117 | 1118 | if (ftime == null) ftime = FrameTime(); 1119 | 1120 | local vel = pplayer.ent.GetVelocity(); 1121 | local veldir = vel + Vector(); 1122 | local absvel = veldir.Norm(); 1123 | 1124 | // Cancel out existing friction calculations 1125 | if (absvel >= 100) { 1126 | vel *= 1 / (1 - ftime * 4); 1127 | } else { 1128 | vel += veldir * (ftime * 400); 1129 | } 1130 | 1131 | // Simulate our own friction 1132 | absvel = vel.Length(); 1133 | 1134 | if (absvel >= 100) { 1135 | vel *= 1 - ftime * fric; 1136 | } else if (fric > 0) { 1137 | if (fric / 0.6 < absvel) { 1138 | vel -= veldir * (ftime * 400); 1139 | } else if (absvel > 0) { 1140 | vel *= mask; 1141 | } 1142 | } 1143 | 1144 | // Apply calculated velocity 1145 | pplayer.ent.SetVelocity(vel); 1146 | 1147 | }; 1148 | 1149 | pplayer.movesim <- function (move, accel = 10, fric = 0, sfric = 0.25, grav = null, ftime = null, eyes = null):(player, pplayer) { 1150 | 1151 | if (ftime == null) ftime = FrameTime(); 1152 | if (eyes == null) eyes = pplayer.eyes; 1153 | if (grav == null) grav = Vector(0, 0, -600); 1154 | 1155 | if (!pplayer.grounded()) accel *= sfric; 1156 | 1157 | local vel = player.GetVelocity(); 1158 | local mask = Vector(0, 0, 1); 1159 | 1160 | if (fric > 0) { 1161 | 1162 | local veldir = vel + Vector(); 1163 | local absvel = veldir.Norm(); 1164 | 1165 | if (absvel >= 100) { 1166 | vel *= 1 - ftime * fric; 1167 | } else if (fric / 0.6 < absvel) { 1168 | vel -= veldir * (ftime * 400); 1169 | } else if (absvel > 0) { 1170 | vel *= mask; 1171 | } 1172 | 1173 | } 1174 | 1175 | local forward = eyes.GetForwardVector(); 1176 | local left = eyes.GetLeftVector(); 1177 | forward -= forward * mask; 1178 | left -= left * mask; 1179 | 1180 | forward.Norm(); 1181 | left.Norm(); 1182 | 1183 | local wishvel = Vector(); 1184 | wishvel.x = forward.x * move.y + left.x * move.x; 1185 | wishvel.y = forward.y * move.y + left.y * move.x; 1186 | wishvel.z = forward.z * move.y + left.z * move.x; 1187 | wishvel -= wishvel * mask; 1188 | local wishspeed = wishvel.Norm(); 1189 | 1190 | local horizvel = vel - vel * mask; 1191 | local currspeed = horizvel.Dot(wishvel); 1192 | 1193 | local addspeed = wishspeed - currspeed; 1194 | local accelspeed = accel * ftime * wishspeed; 1195 | if (accelspeed > addspeed) accelspeed = addspeed; 1196 | 1197 | local finalvel = vel + wishvel * accelspeed + grav * ftime; 1198 | player.SetVelocity(finalvel); 1199 | 1200 | }; 1201 | 1202 | // Resolve the ppromise once pplayer.eyes returns a valid roll angle value and a trigger_gravity has been created 1203 | // These are typically the lengthiest operations, hence why we're checking for these in particular 1204 | return ppromise(function (resolve, reject):(pplayer) { 1205 | 1206 | local intervalname = UniqueString("player_enable"); 1207 | ppmod.interval(function ():(resolve, pplayer, intervalname) { 1208 | 1209 | if (pplayer.eyes.GetAngles().z != 90.0 && ("ppmod_player_gravity" in pplayer.ent.GetScriptScope())) { 1210 | resolve(pplayer); 1211 | Entities.FindByName(null, intervalname).Destroy(); 1212 | } 1213 | 1214 | }, 0, intervalname); 1215 | 1216 | }); 1217 | 1218 | } 1219 | 1220 | ::ppmod.portal <- function (portal) { 1221 | 1222 | if (!portal.ValidateScriptScope()) { 1223 | throw "[ppmod] Error: Could not validate entity script scope in ppmod.portal"; 1224 | } 1225 | 1226 | local scope = portal.GetScriptScope(); 1227 | if ("ppmod_portal" in scope) return scope.ppmod_portal; 1228 | 1229 | local trigger = Entities.CreateByClassname("trigger_multiple"); 1230 | 1231 | trigger.__KeyValueFromInt("Solid", 3); 1232 | trigger.SetAbsOrigin(portal.GetOrigin()); 1233 | trigger.SetForwardVector(portal.GetForwardVector()); 1234 | trigger.SetSize(Vector(-8, -32, -56), Vector(0, 32, 56)); 1235 | 1236 | trigger.__KeyValueFromInt("CollisionGroup", 10); 1237 | trigger.__KeyValueFromInt("SpawnFlags", 11); 1238 | EntFireByHandle(trigger, "Enable", "", 0.0, null, null); 1239 | 1240 | local scrq_idx = ppmod.scrq_add(function (ent):(scope) { 1241 | ppmod.runscript("worldspawn", function ():(ent, scope) { 1242 | 1243 | local ticks_now = (Time() / FrameTime()).tointeger(); 1244 | local ticks_tp = (scope.ppmod_portal.tptime / FrameTime()).tointeger(); 1245 | 1246 | // 1 tick tolerance, ideally 0 one day 1247 | if (ticks_now - ticks_tp > 1) return; 1248 | 1249 | for (local i = 0; i < scope.ppmod_portal.tpfunc.len(); i ++) { 1250 | scope.ppmod_portal.tpfunc[i](ent); 1251 | } 1252 | 1253 | }); 1254 | }, -1); 1255 | 1256 | trigger.__KeyValueFromString("OnEndTouch", "worldspawn\x001BRunScriptCode\x001Bppmod.scrq_get(" + scrq_idx + ")(activator)\x001B0\x001B-1"); 1257 | portal.__KeyValueFromString("OnEntityTeleportFromMe", "!self\x001BRunScriptCode\x001Bself.GetScriptScope().ppmod_portal.tptime<-Time()\x001B0\x001B-1"); 1258 | 1259 | EntFireByHandle(trigger, "SetParent", "!activator", 0.0, portal, null); 1260 | 1261 | local OnTeleport = function (func):(scope) { 1262 | scope.ppmod_portal.tpfunc.push(func); 1263 | }; 1264 | 1265 | local new_detector = function (allids):(portal) { 1266 | 1267 | local detector = Entities.CreateByClassname("func_portal_detector"); 1268 | 1269 | detector.__KeyValueFromInt("Solid", 3); 1270 | detector.SetAbsOrigin(portal.GetOrigin()); 1271 | detector.SetSize(Vector(-0.1, -0.1, -0.1), Vector(0.1, 0.1, 0.1)); 1272 | 1273 | detector.__KeyValueFromInt("CollisionGroup", 10); 1274 | detector.__KeyValueFromInt("CheckAllIDs", allids); 1275 | 1276 | EntFireByHandle(detector, "Enable", "", 0.0, null, null); 1277 | 1278 | return detector; 1279 | 1280 | }; 1281 | 1282 | local GetColor = function ():(new_detector) { 1283 | return ppromise(function (resolve, reject):(new_detector) { 1284 | 1285 | local scrq_idx = ppmod.scrq_add(resolve, 1); 1286 | 1287 | local detector = new_detector(1); 1288 | detector.__KeyValueFromString("OnStartTouchPortal1", "!self\x001BRunScriptCode\x001Bppmod.scrq_get(" + scrq_idx + ")(1);self.Destroy()\x001B0\x001B1"); 1289 | detector.__KeyValueFromString("OnStartTouchPortal2", "!self\x001BRunScriptCode\x001Bppmod.scrq_get(" + scrq_idx + ")(2);self.Destroy()\x001B0\x001B1"); 1290 | 1291 | }); 1292 | }; 1293 | 1294 | local GetActivatedState = function ():(new_detector) { 1295 | return ppromise(function (resolve, reject):(new_detector) { 1296 | 1297 | local scrq_idx = ppmod.scrq_add(resolve, 1); 1298 | 1299 | local detector = new_detector(1); 1300 | detector.__KeyValueFromString("OnStartTouchLinkedPortal", "!self\x001BRunScriptCode\x001Bppmod.scrq_get(" + scrq_idx + ")(true);self.Destroy()\x001B0\x001B1"); 1301 | detector.__KeyValueFromString("OnUser1", "!self\x001BRunScriptCode\x001Bif(self.IsValid())ppmod.scrq_get(" + scrq_idx + ")(false)\x001B0\x001B1"); 1302 | detector.__KeyValueFromString("OnUser1", "!self\x001BKill\x001B\x001B0\x001B1"); 1303 | EntFireByHandle(detector, "FireUser1", "", 0.0, null, null); 1304 | 1305 | }); 1306 | }; 1307 | 1308 | local GetLinkageGroupID = function ():(new_detector, portal) { 1309 | return ppromise(function (resolve, reject):(new_detector, portal) { 1310 | 1311 | local detector = new_detector(0); 1312 | local params = { id = 0 }; 1313 | 1314 | local check = function ():(detector, params, portal) { 1315 | 1316 | if (!detector.IsValid()) return; 1317 | params.id ++; 1318 | 1319 | detector.__KeyValueFromInt("LinkageGroupID", params.id); 1320 | detector.SetAbsOrigin(portal.GetOrigin()); 1321 | EntFireByHandle(detector, "Enable", "", 0.0, null, null); 1322 | 1323 | EntFireByHandle(detector, "FireUser1", "", 0.0, null, null); 1324 | 1325 | }; 1326 | 1327 | local scrq_idx_resolve = ppmod.scrq_add(resolve, 1); 1328 | local scrq_idx_params = ppmod.scrq_add(params, 1); 1329 | local scrq_idx_check = ppmod.scrq_add(check, -1); 1330 | 1331 | detector.__KeyValueFromString("OnStartTouchPortal", "!self\x001BRunScriptCode\x001Bppmod.scrq_get(" + scrq_idx_resolve + ")(ppmod.scrq_get(" + scrq_idx_params + ").id);ppmod.scrq[" + scrq_idx_check + "] = null;self.Destroy()\x001B0\x001B1"); 1332 | detector.__KeyValueFromString("OnUser1", "!self\x001BRunScriptCode\x001Bif(self.IsValid())ppmod.scrq_get(" + scrq_idx_check + ")()\x001B0\x001B-1"); 1333 | 1334 | EntFireByHandle(detector, "FireUser1", "", 0.0, null, null); 1335 | 1336 | }); 1337 | }; 1338 | 1339 | local GetPartnerInstance = function ():(portal, GetLinkageGroupID) { 1340 | return ppromise(function (resolve, reject):(portal, GetLinkageGroupID) { 1341 | 1342 | GetLinkageGroupID().then(function (id):(resolve, portal) { 1343 | 1344 | local param = {}; 1345 | param.next <- function (curr):(id, resolve, portal, param) { 1346 | 1347 | curr = Entities.FindByClassname(curr, "prop_portal"); 1348 | if (curr == null) return resolve(null); 1349 | 1350 | if (curr == portal) return param.next(curr); 1351 | local pportal = ppmod.portal(curr); 1352 | 1353 | pportal.GetLinkageGroupID().then(function (currid):(resolve, param, curr, pportal, id) { 1354 | 1355 | if (currid != id) return param.next(curr); 1356 | 1357 | pportal.GetActivatedState().then(function (state):(resolve, param, curr) { 1358 | if (state) return resolve(curr); 1359 | return param.next(curr); 1360 | }); 1361 | 1362 | }); 1363 | 1364 | }; 1365 | 1366 | param.next(null); 1367 | 1368 | }); 1369 | 1370 | }); 1371 | }; 1372 | 1373 | scope.ppmod_portal <- { 1374 | tptime = 0.0, 1375 | tpfunc = [], 1376 | OnTeleport = OnTeleport, 1377 | GetColor = GetColor, 1378 | GetActivatedState = GetActivatedState, 1379 | GetLinkageGroupID = GetLinkageGroupID, 1380 | GetPartnerInstance = GetPartnerInstance 1381 | }; 1382 | 1383 | return scope.ppmod_portal; 1384 | 1385 | } 1386 | 1387 | ::ppmod.onportal_func <- []; 1388 | ::ppmod.onportal <- function (func) { 1389 | 1390 | ppmod.onportal_func.push(func); 1391 | if (ppmod.onportal_func.len() != 1) return; 1392 | 1393 | ::ppmod.onportal_check <- function (portal, first) { 1394 | 1395 | ppmod.runscript("worldspawn", function ():(portal, first) { 1396 | 1397 | local pgun = null; 1398 | local color = null; 1399 | 1400 | while (pgun = Entities.FindByClassname(pgun, "weapon_portalgun")) { 1401 | 1402 | local scope = pgun.GetScriptScope(); 1403 | 1404 | if (scope["ppmod_onportal_attack_time"] == Time()) { 1405 | color = 1; 1406 | break; 1407 | } 1408 | 1409 | if (scope["ppmod_onportal_attack2_time"] == Time()) { 1410 | color = 2; 1411 | break; 1412 | } 1413 | 1414 | } 1415 | 1416 | if (color == null) pgun = null; 1417 | 1418 | for (local i = 0; i < ppmod.onportal_func.len(); i ++) { 1419 | ppmod.onportal_func[i]({ 1420 | portal = portal, 1421 | weapon = pgun, 1422 | color = color, 1423 | first = first 1424 | }); 1425 | } 1426 | 1427 | }); 1428 | 1429 | }; 1430 | 1431 | ppmod.interval(function () { 1432 | 1433 | local pgun = null; 1434 | while (pgun = Entities.FindByClassname(pgun, "weapon_portalgun")) { 1435 | 1436 | if (!pgun.IsValid()) continue; 1437 | if (!pgun.ValidateScriptScope()) continue; 1438 | 1439 | local scope = pgun.GetScriptScope(); 1440 | if ("ppmod_onportal_attack_time" in scope) continue; 1441 | 1442 | scope.ppmod_onportal_attack_time <- 0.0; 1443 | scope.ppmod_onportal_attack2_time <- 0.0; 1444 | 1445 | pgun.__KeyValueFromString("OnFiredPortal1", "!self\x001BRunScriptCode\x001Bself.GetScriptScope().ppmod_onportal_attack_time<-Time()\x001B0\x001B-1"); 1446 | pgun.__KeyValueFromString("OnFiredPortal2", "!self\x001BRunScriptCode\x001Bself.GetScriptScope().ppmod_onportal_attack2_time<-Time()\x001B0\x001B-1"); 1447 | 1448 | } 1449 | 1450 | local portal = null; 1451 | while (portal = Entities.FindByClassname(portal, "prop_portal")) { 1452 | 1453 | if (!portal.IsValid()) continue; 1454 | if (!portal.ValidateScriptScope()) continue; 1455 | 1456 | local scope = portal.GetScriptScope(); 1457 | if ("ppmod_onportal_flag" in scope) continue; 1458 | 1459 | scope["ppmod_onportal_flag"] <- true; 1460 | 1461 | portal.__KeyValueFromString("OnPlacedSuccessfully", "!self\x001BRunScriptCode\x001Bppmod.onportal_check(self,false)\x001B0\x001B-1"); 1462 | ppmod.onportal_check(portal, true); 1463 | 1464 | } 1465 | 1466 | }); 1467 | 1468 | } 1469 | 1470 | /*******************/ 1471 | // World interface // 1472 | /*******************/ 1473 | 1474 | ::ppmod.create <- function (cmd, key = null) { 1475 | 1476 | // The key is the string used to look for the entity after spawning 1477 | // If no key is provided, we guess it from the input command 1478 | if (!key) { 1479 | 1480 | switch (cmd.slice(0, min(cmd.len(), 17))) { 1481 | 1482 | case "ent_create_portal": key = "cube"; break; 1483 | case "ent_create_paint_": key = "prop_paint_bomb"; break; 1484 | 1485 | default: 1486 | if (cmd.find(" ")) { // In most cases, a space means the command argument is a valid key 1487 | key = cmd.slice(cmd.find(" ") + 1); 1488 | if (key.slice(-4) == ".mdl") key = "models/" + key; 1489 | } else if (cmd.slice(-4) == ".mdl") { // If provided only a model, assume we're using 'prop_dynamic_create' 1490 | key = "models/" + cmd; 1491 | cmd = "prop_dynamic_create " + cmd; 1492 | } else { // If all else fails, assume we're given an entity classname, therefore use 'ent_create' 1493 | key = cmd; 1494 | cmd = "ent_create " + cmd; 1495 | } 1496 | 1497 | } 1498 | } 1499 | 1500 | SendToConsole(cmd); 1501 | 1502 | return ppromise(function (resolve, reject):(cmd, key) { 1503 | 1504 | // Find the entity by passing key to ppmod.prev 1505 | // We send this as a console command to take advantage of how console commands are executed synchronously 1506 | // This lets us make sure that the entity has spawned and that we're looking for it right away 1507 | SendToConsole("script ppmod.scrq_get(" + ppmod.scrq_add(resolve, 1) + ")(ppmod.prev(\"" + key + "\"))"); 1508 | 1509 | }); 1510 | 1511 | } 1512 | 1513 | ::ppmod.give <- function (classname, amount = 1) { 1514 | 1515 | local player = Entities.FindByClassname(null, "player"); 1516 | local equip = Entities.CreateByClassname("game_player_equip"); 1517 | 1518 | // The keyvalue sets the entity classname and amount 1519 | equip.__KeyValueFromInt(classname, amount); 1520 | EntFireByHandle(equip, "Use", "", 0.0, player, null); 1521 | 1522 | return ppromise(function (resolve, reject):(classname, amount, equip) { 1523 | 1524 | local scrq_id = ppmod.scrq_add(function ():(resolve, classname, amount) { 1525 | 1526 | local arr = array(amount); 1527 | local curridx = 0; 1528 | 1529 | local ent = null; 1530 | while (ent = Entities.FindByClassname(ent, classname)) { 1531 | arr[curridx] = ent; 1532 | if (++curridx == amount) curridx = 0; 1533 | } 1534 | 1535 | resolve(arr); 1536 | 1537 | }, 1); 1538 | 1539 | EntFireByHandle(equip, "RunScriptCode", "ppmod.scrq_get(" + scrq_id + ")()", 0.0, null, null); 1540 | EntFireByHandle(equip, "Kill", "", 0.0, null, null); 1541 | 1542 | }); 1543 | 1544 | } 1545 | 1546 | ::ppmod.brush <- function (pos, size, type = "func_brush", ang = Vector(), create = false) { 1547 | 1548 | if (!create) { 1549 | 1550 | local brush = type; 1551 | if (typeof type == "string") brush = Entities.CreateByClassname(type); 1552 | 1553 | brush.__KeyValueFromInt("Solid", 3); 1554 | brush.SetAbsOrigin(pos); 1555 | brush.SetAngles(ang.x, ang.y, ang.z); 1556 | brush.SetSize(Vector() - size, size); 1557 | 1558 | return brush; 1559 | 1560 | } 1561 | 1562 | return ppromise(function (resolve, reject):(type, pos, size, ang) { 1563 | 1564 | ppmod.create(type).then(function (ent):(pos, size, ang, resolve) { 1565 | 1566 | resolve(ppmod.brush(pos, size, ent, ang)); 1567 | 1568 | }); 1569 | 1570 | }); 1571 | 1572 | } 1573 | 1574 | ::ppmod.trigger <- function (pos, size, type = "trigger_once", ang = Vector(), create = false) { 1575 | 1576 | if (!create) { 1577 | 1578 | local trigger = type; 1579 | if (typeof type == "string") trigger = ppmod.brush(pos, size, type, ang); 1580 | 1581 | trigger.__KeyValueFromInt("CollisionGroup", 10); 1582 | trigger.__KeyValueFromInt("SpawnFlags", 1); 1583 | EntFireByHandle(trigger, "Enable", "", 0.0, null, null); 1584 | 1585 | if (type == "trigger_once") { 1586 | trigger.__KeyValueFromString("OnStartTouch", "!self\x001BKill\x1B\x001B0\x001B1"); 1587 | } 1588 | 1589 | return trigger; 1590 | 1591 | } 1592 | 1593 | return ppromise(function (resolve, reject):(pos, size, type, ang) { 1594 | 1595 | ppmod.brush(pos, size, type, ang, true).then(function (ent):(pos, size, ang, resolve) { 1596 | 1597 | resolve(ppmod.trigger(pos, size, ent, ang)); 1598 | 1599 | }); 1600 | 1601 | }); 1602 | 1603 | } 1604 | 1605 | ::ppmod.project <- function (material, pos, ang = Vector(90, 0, 0), simple = 0, far = 128) { 1606 | 1607 | local texture = Entities.CreateByClassname("env_projectedtexture"); 1608 | 1609 | texture.SetAbsOrigin(pos); 1610 | texture.SetAngles(ang.x, ang.y, ang.z); 1611 | texture.__KeyValueFromInt("FarZ", far); 1612 | texture.__KeyValueFromInt("SimpleProjection", simple.tointeger()); 1613 | texture.__KeyValueFromString("TextureName", material); 1614 | 1615 | return texture; 1616 | 1617 | } 1618 | 1619 | ::ppmod.decal <- function (material, pos, ang = Vector(90, 0, 0)) { 1620 | 1621 | local decal = Entities.CreateByClassname("infodecal"); 1622 | 1623 | decal.SetAbsOrigin(pos); 1624 | decal.SetAngles(ang.x, ang.y, ang.z); 1625 | decal.__KeyValueFromString("Texture", material); 1626 | EntFireByHandle(decal, "Activate", "", 0.0, null, null); 1627 | 1628 | return decal; 1629 | 1630 | } 1631 | 1632 | // Set up some dummy entites for simplifying ray-through-portal calculations 1633 | local p_anchor = Entities.CreateByClassname("info_target"); 1634 | local r_anchor = Entities.CreateByClassname("info_target"); 1635 | 1636 | p_anchor.__KeyValueFromString("Targetname", "ppmod_portals_p_anchor"); 1637 | r_anchor.__KeyValueFromString("Targetname", "ppmod_portals_r_anchor"); 1638 | 1639 | EntFireByHandle(r_anchor, "SetParent", "ppmod_portals_p_anchor", 0.0, null, null); 1640 | 1641 | ::ppmod.ray <- function (start, end, ent = null, world = true, portals = null, ray = null) { 1642 | 1643 | local formatreturn = function (fraction, ray, hitent = null):(start, end, ent, world, portals) { 1644 | 1645 | if (world) fraction = min(fraction, TraceLine(start, end, null)); 1646 | local dirvec = end - start; 1647 | 1648 | local output = { 1649 | fraction = fraction, 1650 | point = start + dirvec * fraction, 1651 | entity = hitent 1652 | }; 1653 | 1654 | if (!portals) return output; 1655 | if (typeof portals != "array") return output; 1656 | if (portals.len() < 2) return output; 1657 | 1658 | // Check if we're intersecting the bounding box of one of the provided portals 1659 | local portal = Entities.FindByClassnameWithin(null, "prop_portal", output.point, 1.0); 1660 | if (portal != portals[0] && portal != portals[1]) return output; 1661 | 1662 | // Determine which portal is the other portal 1663 | local other = (portal == portals[0]) ? portals[1] : portals[0]; 1664 | 1665 | local p_anchor = Entities.FindByName(null, "ppmod_portals_p_anchor"); 1666 | local r_anchor = Entities.FindByName(null, "ppmod_portals_r_anchor"); 1667 | 1668 | // Set portal anchor facing the entry portal 1669 | p_anchor.SetForwardVector(Vector() - portal.GetForwardVector()); 1670 | 1671 | // Set positions of anchors to entry portal origin and ray endpoint, respectively 1672 | p_anchor.SetAbsOrigin(portal.GetOrigin()); 1673 | r_anchor.SetAbsOrigin(output.point); 1674 | 1675 | // Translate both anchor points to exit portal (r_anchor is parented to p_anchor) 1676 | p_anchor.SetAbsOrigin(other.GetOrigin()); 1677 | 1678 | // Calculate angles from vector of ray direction 1679 | // First, normalize the vector to get a unit vector 1680 | local len = dirvec.Norm(); 1681 | 1682 | // Then, calculate yaw, pitch and roll in degrees 1683 | local yaw = atan2(dirvec.y, dirvec.x) / PI * 180; 1684 | local pitch = asin(-dirvec.z) / PI * 180; 1685 | local roll = atan2(dirvec.z, sqrt(dirvec.x * dirvec.x + dirvec.y * dirvec.y)) / PI * 180; 1686 | 1687 | // Due to being parented, r_anchor's angles are usually relative to p_anchor 1688 | // The "angles" keyvalue, however, is absolute 1689 | r_anchor.__KeyValueFromString("angles", pitch + " " + yaw + " " + roll); 1690 | // Finally, rotate the portal anchor to get ray starting position and direction 1691 | p_anchor.SetForwardVector(other.GetForwardVector()); 1692 | 1693 | local newstart = r_anchor.GetOrigin(); 1694 | 1695 | // Check if the new starting point is behind the exit portal 1696 | local offset = newstart - other.GetOrigin(); 1697 | local epsilon = 0.000001; // Some flat walls are not flat... 1698 | 1699 | if (other.GetForwardVector().x > epsilon && offset.x < -epsilon) return output; 1700 | if (other.GetForwardVector().x < -epsilon && offset.x > epsilon) return output; 1701 | 1702 | if (other.GetForwardVector().y > epsilon && offset.y < -epsilon) return output; 1703 | if (other.GetForwardVector().y < -epsilon && offset.y > epsilon) return output; 1704 | 1705 | if (other.GetForwardVector().z > epsilon && offset.z < -epsilon) return output; 1706 | if (other.GetForwardVector().z < -epsilon && offset.z > epsilon) return output; 1707 | 1708 | local newend = r_anchor.GetOrigin() + r_anchor.GetForwardVector() * (len * (1.0 - fraction)); 1709 | 1710 | return ppmod.ray(newstart, newend, ent, world, portals); 1711 | 1712 | }; 1713 | 1714 | if (!ent) return formatreturn( 1.0, ray ); 1715 | 1716 | local len, div; 1717 | if (!ray) { 1718 | local dir = end - start; 1719 | len = dir.Norm(); 1720 | div = [1.0 / dir.x, 1.0 / dir.y, 1.0 / dir.z]; 1721 | } else { 1722 | len = ray[0]; 1723 | div = ray[1]; 1724 | } 1725 | 1726 | // Defines behavior when multiple valid entries are provided - returns the lowest fraction among them 1727 | if (typeof ent == "array") { 1728 | 1729 | // If an array contains only two Vectors, treat those instead as the origin point and half-widths of an entity, respectively 1730 | local isbbox = false; 1731 | if (ent.len() == 2) if (typeof ent[0] == "Vector" && typeof ent[1] == "Vector") { 1732 | 1733 | local pos = ent[0], size = ent[1]; 1734 | 1735 | ent = { 1736 | GetOrigin = function ():(pos) { return pos }, 1737 | GetAngles = function () { return Vector() }, 1738 | GetBoundingMaxs = function ():(size) { return size }, 1739 | GetBoundingMins = function ():(size) { return Vector() - size }, 1740 | }; 1741 | 1742 | isbbox = true; 1743 | 1744 | } 1745 | 1746 | // Squirrel sucks, we can't just have an 'else' here 1747 | if (!isbbox) { 1748 | 1749 | local closest = ppmod.ray(start, end, ent[0], false, portals, [len, div]); 1750 | for (local i = 1; i < ent.len(); i ++) { 1751 | local curr = ppmod.ray(start, end, ent[i], false, portals, [len, div]); 1752 | if (curr.fraction < closest.fraction) closest = curr; 1753 | } 1754 | return formatreturn( closest.fraction, [len, div], closest.entity ); 1755 | 1756 | } 1757 | 1758 | } else if (typeof ent == "string") { 1759 | 1760 | local next = ppmod.get(ent); 1761 | local closest = ppmod.ray(start, end, next, false, portals, [len, div]); 1762 | while (next = ppmod.get(ent, next)) { 1763 | local curr = ppmod.ray(start, end, next, false, portals, [len, div]); 1764 | if (curr.fraction < closest.fraction) closest = curr; 1765 | } 1766 | return formatreturn( closest.fraction, [len, div], closest.entity ); 1767 | 1768 | } 1769 | 1770 | local pos = ent.GetOrigin(); 1771 | 1772 | local mins = ent.GetBoundingMins(); 1773 | local maxs = ent.GetBoundingMaxs(); 1774 | 1775 | local minmin = min(mins.x, min(mins.y, mins.z)); 1776 | local maxmax = max(maxs.x, max(maxs.y, maxs.z)); 1777 | 1778 | if (pos.x + minmin > max(start.x, end.x)) return formatreturn( 1.0, [len, div] ); 1779 | if (pos.x + maxmax < min(start.x, end.x)) return formatreturn( 1.0, [len, div] ); 1780 | 1781 | if (pos.y + minmin > max(start.y, end.y)) return formatreturn( 1.0, [len, div] ); 1782 | if (pos.y + maxmax < min(start.y, end.y)) return formatreturn( 1.0, [len, div] ); 1783 | 1784 | if (pos.z + minmin > max(start.z, end.z)) return formatreturn( 1.0, [len, div] ); 1785 | if (pos.z + maxmax < min(start.z, end.z)) return formatreturn( 1.0, [len, div] ); 1786 | 1787 | local ang = ent.GetAngles() * (PI / 180.0); 1788 | local c1 = cos(ang.z); 1789 | local s1 = sin(ang.z); 1790 | local c2 = cos(ang.x); 1791 | local s2 = sin(ang.x); 1792 | local c3 = cos(ang.y); 1793 | local s3 = sin(ang.y); 1794 | 1795 | local matrix = [ 1796 | [c2 * c3, c3 * s1 * s2 - c1 * s3, s1 * s3 + c1 * c3 * s2], 1797 | [c2 * s3, c1 * c3 + s1 * s2 * s3, c1 * s2 * s3 - c3 * s1], 1798 | [-s2, c2 * s1, c1 * c2] 1799 | ]; 1800 | 1801 | mins = [mins.x, mins.y, mins.z]; 1802 | maxs = [maxs.x, maxs.y, maxs.z]; 1803 | 1804 | local bmin = [pos.x, pos.y, pos.z]; 1805 | local bmax = [pos.x, pos.y, pos.z]; 1806 | local a, b; 1807 | 1808 | for (local i = 0; i < 3; i ++) { 1809 | for (local j = 0; j < 3; j ++) { 1810 | a = (matrix[i][j] * mins[j]); 1811 | b = (matrix[i][j] * maxs[j]); 1812 | if(a < b) { 1813 | bmin[i] += a; 1814 | bmax[i] += b; 1815 | } else { 1816 | bmin[i] += b; 1817 | bmax[i] += a; 1818 | } 1819 | } 1820 | } 1821 | 1822 | if ( 1823 | start.x > bmin[0] && start.x < bmax[0] && 1824 | start.y > bmin[1] && start.y < bmax[1] && 1825 | start.z > bmin[2] && start.z < bmax[2] 1826 | ) return formatreturn( 0.0, [len, div], ent ); 1827 | 1828 | start = [start.x, start.y, start.z]; 1829 | 1830 | local tmin = [0.0, 0.0, 0.0]; 1831 | local tmax = [0.0, 0.0, 0.0]; 1832 | 1833 | for (local i = 0; i < 3; i ++) { 1834 | if (div[i] >= 0) { 1835 | tmin[i] = (bmin[i] - start[i]) * div[i]; 1836 | tmax[i] = (bmax[i] - start[i]) * div[i]; 1837 | } else { 1838 | tmin[i] = (bmax[i] - start[i]) * div[i]; 1839 | tmax[i] = (bmin[i] - start[i]) * div[i]; 1840 | } 1841 | if (tmin[0] > tmax[i] || tmin[i] > tmax[0]) return formatreturn( 1.0, [len, div] ); 1842 | if (tmin[i] > tmin[0]) tmin[0] = tmin[i]; 1843 | if (tmax[i] < tmax[0]) tmax[0] = tmax[i]; 1844 | } 1845 | 1846 | if (tmin[0] < 0) tmin[0] = 1.0; 1847 | else tmin[0] /= len; 1848 | 1849 | return formatreturn( tmin[0], [len, div], ent ); 1850 | 1851 | } 1852 | 1853 | ::ppmod.inbounds <- function (point) { 1854 | 1855 | if (TraceLine(point, point + Vector(65536, 0, 0), null) == 1.0) return false; 1856 | if (TraceLine(point, point - Vector(65536, 0, 0), null) == 1.0) return false; 1857 | if (TraceLine(point, point + Vector(0, 65536, 0), null) == 1.0) return false; 1858 | if (TraceLine(point, point - Vector(0, 65536, 0), null) == 1.0) return false; 1859 | if (TraceLine(point, point + Vector(0, 0, 65536), null) == 1.0) return false; 1860 | if (TraceLine(point, point - Vector(0, 0, 65536), null) == 1.0) return false; 1861 | 1862 | return true; 1863 | 1864 | } 1865 | 1866 | ::ppmod.button <- function (type, pos, ang = Vector()) { 1867 | 1868 | // Ensure that sounds are precached by creating a dummy entity 1869 | ppmod.create(type).then(function (dummy) { 1870 | dummy.Destroy(); 1871 | }); 1872 | 1873 | local model; 1874 | 1875 | if (type == "prop_button") model = "props/switch001.mdl"; 1876 | if (type == "prop_under_button") model = "props_underground/underground_testchamber_button.mdl"; 1877 | if (type == "prop_floor_button") model = "props/portal_button.mdl"; 1878 | if (type == "prop_floor_cube_button") model = "props/box_socket.mdl"; 1879 | if (type == "prop_floor_ball_button") model = "props/ball_button.mdl"; 1880 | if (type == "prop_under_floor_button") model = "props_underground/underground_floor_button.mdl"; 1881 | 1882 | return ppromise(function (resolve, reject):(type, pos, ang, model) { 1883 | 1884 | // First, create a prop_dynamic with the appropriate model 1885 | ppmod.create(model).then(function (ent):(type, pos, ang, resolve) { 1886 | 1887 | ent.SetAbsOrigin(pos); 1888 | ent.SetAngles(ang.x, ang.y, ang.z); 1889 | 1890 | // The floor buttons often come with additional phys_bone_followers 1891 | while (ent.GetClassname() == "phys_bone_follower") { 1892 | ent = ppmod.prev(ent.GetModelName(), ent); 1893 | ent.SetAbsOrigin(pos); 1894 | ent.SetAngles(ang.x, ang.y, ang.z); 1895 | } 1896 | 1897 | if (type == "prop_button" || type == "prop_under_button") { // Handle pedestal buttons 1898 | 1899 | // func_button seems to be broken when spawned during runtime, hence the use of func_rot_button 1900 | ppmod.brush(pos + (ent.GetUpVector() * 40), Vector(8, 8, 8), "func_rot_button", ang, true).then(function (button):(type, ent, resolve) { 1901 | 1902 | // Make the button box non-solid and activated with +use 1903 | button.__KeyValueFromInt("CollisionGroup", 2); 1904 | button.__KeyValueFromInt("SpawnFlags", 1024); 1905 | ppmod.setparent(button, ent); 1906 | 1907 | // Properties are stored in the func_rot_button's script scope 1908 | button.ValidateScriptScope(); 1909 | button.GetScriptScope()["button_delay"] <- 1.0; 1910 | button.GetScriptScope()["button_timer"] <- false; 1911 | button.GetScriptScope()["button_permanent"] <- false; 1912 | 1913 | ppmod.addscript(button, "OnPressed", function ():(type, ent, button) { 1914 | 1915 | // Underground buttons have different animation names 1916 | // The additional sound effects for those are baked into the animation 1917 | if (type == "prop_button") EntFireByHandle(ent, "SetAnimation", "down", 0.0, null, null); 1918 | else EntFireByHandle(ent, "SetAnimation", "press", 0.0, null, null); 1919 | button.EmitSound("Portal.button_down"); 1920 | 1921 | // To disable the button while it's down, we clear its "+use activates" flag 1922 | button.__KeyValueFromInt("SpawnFlags", 0); 1923 | 1924 | local timer = null; // Simulate the timer ticks 1925 | if (button.GetScriptScope()["button_timer"]) { 1926 | 1927 | timer = Entities.CreateByClassname("logic_timer"); 1928 | 1929 | ppmod.addscript(timer, "OnTimer", function ():(button) { 1930 | button.EmitSound("Portal.room1_TickTock"); 1931 | }); 1932 | 1933 | // Offset activation by one tick to prevent an extra tick upon release 1934 | EntFireByHandle(timer, "RefireTime", "1", 0.0, null, null); 1935 | EntFireByHandle(timer, "Enable", "", FrameTime(), null, null); 1936 | 1937 | } 1938 | 1939 | // If "permanent", skip the release code 1940 | if (button.GetScriptScope()["button_permanent"]) return; 1941 | 1942 | ppmod.wait(function ():(ent, button, type, timer) { 1943 | 1944 | if (type == "prop_button") EntFireByHandle(ent, "SetAnimation", "up", 0.0, null, null); 1945 | else EntFireByHandle(ent, "SetAnimation", "release", 0.0, null, null); 1946 | button.EmitSound("Portal.button_up"); 1947 | 1948 | button.__KeyValueFromInt("SpawnFlags", 1024); 1949 | if (timer) timer.Destroy(); 1950 | 1951 | }, button.GetScriptScope()["button_delay"]); 1952 | 1953 | }); 1954 | 1955 | resolve({ 1956 | 1957 | GetButton = function ():(button) { return button }, 1958 | GetProp = function ():(ent) { return ent }, 1959 | SetDelay = function (delay):(button) { button.GetScriptScope()["button_delay"] <- delay }, 1960 | SetTimer = function (enabled):(button) { button.GetScriptScope()["button_timer"] <- enabled }, 1961 | SetPermanent = function (enabled):(button) { button.GetScriptScope()["button_permanent"] <- enabled }, 1962 | OnPressed = function (scr):(button) { ppmod.addscript(button, "OnPressed", scr) }, 1963 | 1964 | }); 1965 | 1966 | }); 1967 | 1968 | } else { // Handle floor buttons 1969 | 1970 | // This moves the phys_bone_followers into place 1971 | EntFireByHandle(ent, "SetAnimation", "BindPose", 0.0, null, null); 1972 | 1973 | local trigger; 1974 | if (type == "prop_under_floor_button") { 1975 | trigger = ppmod.trigger(pos + Vector(0, 0, 8.5), Vector(30, 30, 8.5), "trigger_multiple", ang); 1976 | } else { 1977 | trigger = ppmod.trigger(pos + Vector(0, 0, 7), Vector(20, 20, 7), "trigger_multiple", ang); 1978 | } 1979 | 1980 | // Activated by players and physics props 1981 | trigger.__KeyValueFromInt("SpawnFlags", 9); 1982 | 1983 | trigger.ValidateScriptScope(); 1984 | trigger.GetScriptScope()["count"] <- 0; 1985 | 1986 | // Used for attaching output scripts to press and unpress events 1987 | local pressrl = Entities.CreateByClassname("logic_relay"); 1988 | pressrl.__KeyValueFromInt("SpawnFlags", 2); 1989 | local unpressrl = Entities.CreateByClassname("logic_relay"); 1990 | unpressrl.__KeyValueFromInt("SpawnFlags", 2); 1991 | 1992 | local press = function ():(type, trigger, ent, pressrl, unpressrl) { 1993 | if (++trigger.GetScriptScope()["count"] == 1) { 1994 | 1995 | EntFireByHandle(pressrl, "Trigger", "", 0.0, null, null); 1996 | 1997 | if (type == "prop_under_floor_button") { 1998 | EntFireByHandle(ent, "SetAnimation", "press", 0.0, null, null); 1999 | ent.EmitSound("Portal.OGButtonDepress"); 2000 | } else { 2001 | EntFireByHandle(ent, "SetAnimation", "down", 0.0, null, null); 2002 | ent.EmitSound("Portal.ButtonDepress"); 2003 | } 2004 | 2005 | } 2006 | }; 2007 | 2008 | local unpress = function ():(type, trigger, ent) { 2009 | if (--trigger.GetScriptScope()["count"] == 0) { 2010 | 2011 | EntFireByHandle(unpressrl, "Trigger", "", 0.0, null, null); 2012 | 2013 | if (type == "prop_under_floor_button") { 2014 | EntFireByHandle(ent, "SetAnimation", "release", 0.0, null, null); 2015 | ent.EmitSound("Portal.OGButtonRelease"); 2016 | } else { 2017 | EntFireByHandle(ent, "SetAnimation", "up", 0.0, null, null); 2018 | ent.EmitSound("Portal.ButtonRelease"); 2019 | } 2020 | 2021 | } 2022 | }; 2023 | 2024 | // Checks classnames and model names to filter the entities activating the button 2025 | local strpress, strunpress; 2026 | if (type == "prop_floor_button" || type == "prop_under_floor_button") { 2027 | strpress = "if (self.GetClassname() == \"prop_weighted_cube\" || self.GetClassname() == \"player\") ppmod.scrq_get(" + ppmod.scrq_add(press) + ")()"; 2028 | strunpress = "if (self.GetClassname() == \"prop_weighted_cube\" || self.GetClassname() == \"player\") ppmod.scrq_get(" + ppmod.scrq_add(unpress) + ")()"; 2029 | } else if (type == "prop_floor_ball_button") { 2030 | strpress = "if (self.GetClassname() == \"prop_weighted_cube\" && self.GetModelName() == \"models/props_gameplay/mp_ball.mdl\") ppmod.scrq_get(" + ppmod.scrq_add(press) + ")()"; 2031 | strunpress = "if (self.GetClassname() == \"prop_weighted_cube\" && self.GetModelName() == \"models/props_gameplay/mp_ball.mdl\") ppmod.scrq_get(" + ppmod.scrq_add(unpress) + ")()"; 2032 | } else { 2033 | strpress = "if (self.GetClassname() == \"prop_weighted_cube\" && self.GetModelName() != \"models/props_gameplay/mp_ball.mdl\") ppmod.scrq_get(" + ppmod.scrq_add(press) + ")()"; 2034 | strunpress = "if (self.GetClassname() == \"prop_weighted_cube\" && self.GetModelName() != \"models/props_gameplay/mp_ball.mdl\") ppmod.scrq_get(" + ppmod.scrq_add(unpress) + ")()"; 2035 | } 2036 | 2037 | ppmod.addoutput(trigger, "OnStartTouch", "!activator", "RunScriptCode", strpress); 2038 | ppmod.addoutput(trigger, "OnEndTouch", "!activator", "RunScriptCode", strunpress); 2039 | 2040 | resolve({ 2041 | 2042 | GetTrigger = function ():(trigger) { return trigger }, 2043 | GetProp = function ():(ent) { return ent }, 2044 | GetCount = function ():(trigger) { return trigger.GetScriptScope()["count"] }, 2045 | OnPressed = function (scr):(pressrl) { ppmod.addscript(pressrl, "OnTrigger", scr) }, 2046 | OnUnpressed = function (scr):(unpressrl) { ppmod.addscript(unpressrl, "OnTrigger", scr) }, 2047 | 2048 | }); 2049 | 2050 | } 2051 | 2052 | }); 2053 | 2054 | }); 2055 | 2056 | } 2057 | 2058 | ::ppmod.catapult <- function (ent, vec) { 2059 | 2060 | if (!(typeof ent == "instance" && ent instanceof CBaseEntity)) { 2061 | ppmod.getall(ent, function (curr):(vec) { 2062 | ppmod.catapult(curr, vec); 2063 | }); 2064 | return; 2065 | } 2066 | 2067 | local speed = vec.Norm(); 2068 | 2069 | local trigger = Entities.CreateByClassname("trigger_catapult"); 2070 | trigger.__KeyValueFromInt("Solid", 3); 2071 | trigger.SetAbsOrigin(ent.GetOrigin()); 2072 | trigger.SetForwardVector(vec); 2073 | trigger.SetSize(Vector(-0.2, -0.2, -0.2), Vector(0.2, 0.2, 0.2)); 2074 | trigger.__KeyValueFromInt("CollisionGroup", 1); 2075 | 2076 | local ang = trigger.GetAngles(); 2077 | trigger.__KeyValueFromInt("SpawnFlags", 8); 2078 | trigger.__KeyValueFromFloat("PhysicsSpeed", speed); 2079 | trigger.__KeyValueFromString("LaunchDirection", ang.x+" "+ang.y+" "+ang.z); 2080 | 2081 | EntFireByHandle(trigger, "Enable", "", 0.0, null, null); 2082 | EntFireByHandle(trigger, "Kill", "", 0.0, null, null); 2083 | 2084 | } 2085 | 2086 | /******************/ 2087 | // Game interface // 2088 | /******************/ 2089 | 2090 | ::ppmod.text <- class { 2091 | 2092 | ent = null; 2093 | 2094 | constructor (text = "", x = -1.0, y = -1.0) { 2095 | this.ent = Entities.CreateByClassname("game_text"); 2096 | this.ent.__KeyValueFromString("Message", text); 2097 | this.ent.__KeyValueFromString("Color", "255 255 255"); 2098 | this.ent.__KeyValueFromFloat("X", x); 2099 | this.ent.__KeyValueFromFloat("Y", y); 2100 | } 2101 | 2102 | function GetEntity () { 2103 | return this.ent; 2104 | } 2105 | function SetPosition (x, y) { 2106 | this.ent.__KeyValueFromFloat("X", x); 2107 | this.ent.__KeyValueFromFloat("Y", y); 2108 | } 2109 | function SetText (text) { 2110 | this.ent.__KeyValueFromString("Message", text); 2111 | } 2112 | function SetSize (size) { 2113 | // Channels sorted from smallest to biggest font size 2114 | this.ent.__KeyValueFromInt("Channel", [2, 1, 4, 0, 5, 3][size]); 2115 | } 2116 | function SetColor (c1, c2 = null) { 2117 | this.ent.__KeyValueFromString("Color", c1); 2118 | if (c2) this.ent.__KeyValueFromString("Color2", c2); 2119 | } 2120 | function SetFade (fin, fout, fx = false) { 2121 | this.ent.__KeyValueFromFloat("FadeIn", fin); 2122 | this.ent.__KeyValueFromFloat("FXTime", fin); 2123 | this.ent.__KeyValueFromFloat("FadeOut", fout); 2124 | if (fx) this.ent.__KeyValueFromInt("Effect", 2); 2125 | else this.ent.__KeyValueFromInt("Effect", 0); 2126 | } 2127 | function Display (hold = null, player = null) { 2128 | if (hold == null) hold = FrameTime(); 2129 | this.ent.__KeyValueFromFloat("HoldTime", hold); 2130 | if (player) this.ent.__KeyValueFromInt("SpawnFlags", 0); 2131 | else this.ent.__KeyValueFromInt("SpawnFlags", 1); 2132 | EntFireByHandle(ent, "Display", "", 0.0, player, null); 2133 | } 2134 | 2135 | } 2136 | 2137 | ::ppmod.fwrite <- function (path, str) { // EXPERIMENTAL 2138 | 2139 | printl("[ppmod] Warning: fwrite is an experimental feature!"); 2140 | 2141 | local stall = ""; 2142 | for (local i = 195; i > path.len(); i --) stall += "/"; 2143 | 2144 | if (path[0] == "/") path = stall + path; 2145 | else path = "." + stall.slice(1) + path; 2146 | 2147 | for (local i = 0; i < str.len(); i ++) { 2148 | if (str[i] == '\\') str = str.slice(0, i) + "\\\\" + str.slice(++i); 2149 | if (str[i] == '"') str = str.slice(0, i) + "\\\x22" + str.slice(++i); 2150 | } 2151 | 2152 | SendToConsole("con_logfile \"" + path + ".log\""); 2153 | SendToConsole("script print(\"" + str + "\")"); 2154 | SendToConsole("con_logfile \"\""); 2155 | 2156 | } 2157 | 2158 | // ::ppmod.conread <- function (command, callback) { // EXPERIMENTAL 2159 | 2160 | // printl("[ppmod] Warning: conread is an experimental feature!"); 2161 | 2162 | // local path = ".//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////scripts/vscripts/conread.nut"; 2163 | 2164 | // local scr = "ppmod.scrq_get(" + ppmod.scrq_add(callback, 1) + ")"; 2165 | 2166 | // // SendToConsole("writeip"); 2167 | // SendToConsole("con_logfile \"" + path + ".log\""); 2168 | // SendToConsole("script print(\"local output = @\\\"\")"); 2169 | // SendToConsole(command); 2170 | // SendToConsole("script printl(\"\\\"\")"); 2171 | // SendToConsole("script printl(\""+scr+"(output)\")"); 2172 | // SendToConsole("con_logfile \"\""); 2173 | 2174 | // SendToConsole("script_execute conread"); 2175 | 2176 | // } 2177 | --------------------------------------------------------------------------------