hlmod.hu

Magyar Half-Life Mód közösség!
Pontos idő: 2024.11.01. 01:00



Jelenlévő felhasználók

Jelenleg 318 felhasználó van jelen :: 1 regisztrált, 0 rejtett és 317 vendég

A legtöbb felhasználó (1565 fő) 2020.11.21. 11:26-kor tartózkodott itt.

Regisztrált felhasználók: Bing [Bot]az elmúlt 5 percben aktív felhasználók alapján

Utoljára aktív
Ahhoz hogy lásd ki volt utoljára aktív, be kell jelentkezned.



Az oldal teljeskörű
használatához regisztrálj.

Regisztráció

Kereső


Új téma nyitása Hozzászólás a témához  [5 hozzászólás ] 
Szerző Üzenet
 Hozzászólás témája: Ejtőernyő minden játékosnak
HozzászólásElküldve:2016.03.23. 22:22 
Offline
Jómunkásember
Avatar

Csatlakozott:2014.01.11. 22:53
Hozzászólások:379
Megköszönt másnak: 75 alkalommal
Megköszönték neki: 8 alkalommal
Sziasztok
Valaki tudna segíteni, hogy hogyan lehet beállítani, hogy minden játékos ejtőernyők kapjon?

_________________
Kép


Hozzászólás jelentése
Vissza a tetejére
   
 Hozzászólás témája: Re: Ejtőernyő minden játékosnak
HozzászólásElküldve:2016.03.23. 22:52 
Offline
Tiszteletbeli
Avatar

Csatlakozott:2014.09.08. 22:21
Hozzászólások:3014
Megköszönt másnak: 96 alkalommal
Megköszönték neki: 555 alkalommal
  1. #include <amxmodx>
  2. #include <hamsandwich>
  3. #include <engine>
  4.  
  5.  
  6. new g_bAlive[33];
  7.  
  8. public plugin_init() {
  9.     register_plugin("Parachute", "1.0", "Vaqtincha")
  10.  
  11.     RegisterHam(Ham_ObjectCaps, "player", "fwdhamobjectcaps")
  12.     RegisterHam(Ham_Spawn, "player", "playerspawn_post", 1)
  13.     RegisterHam(Ham_Killed, "player", "playerkilled_post", 1)
  14. }
  15.  
  16. public client_putinserver(id)
  17. {
  18.     g_bAlive[id] = 0
  19. }
  20.  
  21. public playerspawn_post(id)
  22. {
  23.     g_bAlive[id] = is_user_alive(id)
  24. }
  25.  
  26. public playerkilled_post(id) g_bAlive[id] = 0
  27.  
  28. public fwdhamobjectcaps(id)
  29. {
  30.     if(!g_bAlive[id] || ~get_user_button(id) & IN_USE
  31.     || get_entity_flags(id) & FL_ONGROUND) {
  32.         return
  33.     }
  34.     static Float:velocity[3]; entity_get_vector(id, EV_VEC_velocity, velocity)
  35.     if(velocity[2] < 0) {
  36.         velocity[2] = (velocity[2] + 40.0 < -100) ? velocity[2] + 40.0 : -100.0
  37.         entity_set_vector(id, EV_VEC_velocity, velocity)
  38.     }
  39. }

_________________
GitHub - mforce

Ők köszönték meg mforce nek ezt a hozzászólást: titkos (2016.03.24. 08:54)
  Népszerűség: 2.27%


Hozzászólás jelentése
Vissza a tetejére
   
 Hozzászólás témája: Re: Ejtőernyő minden játékosnak
HozzászólásElküldve:2016.03.24. 08:50 
Offline
Félisten
Avatar

Csatlakozott:2016.03.06. 14:20
Hozzászólások:973
Megköszönt másnak: 109 alkalommal
Megköszönték neki: 136 alkalommal
titkos írta:
Sziasztok
Valaki tudna segíteni, hogy hogyan lehet beállítani, hogy minden játékos ejtőernyők kapjon?


amxx.cfg-be írd be :
Kód:
parachute_cost 0



Miért írjam be?

- Mert ez azt csinálja neked hogy: az ejtőernyő árát 0-ra rakja, és így automatikusan kapja az illető az ejtőernyő-t, de ezt persze pluginon belül is meglehet oldani hogy ne kelljen beleírni az amxx.cfg-be és csak a plugin-t berakod és kész...

Tessék:

  1. #include <amxmodx>
  2.     #include <amxmisc>
  3.     #include <engine>
  4.     #include <cstrike>
  5.     #include <fun>
  6.      
  7.     new bool:has_parachute[33]
  8.     new para_ent[33]
  9.     new gCStrike = 0
  10.     new pDetach, pFallSpeed, pEnabled, pCost, pPayback
  11.      
  12.     #define PARACHUTE_LEVEL ADMIN_LEVEL_A
  13.      
  14.     public plugin_init()
  15.     {
  16.         register_plugin("Parachute", "1.3", "KRoT@L/JTP10181")
  17.         pEnabled = register_cvar("sv_parachute", "1" )
  18.         pFallSpeed = register_cvar("parachute_fallspeed", "100")
  19.         pDetach = register_cvar("parachute_detach", "1")
  20.      
  21.         if (cstrike_running()) gCStrike = true
  22.      
  23.         if (gCStrike) {
  24.      
  25.             pCost = register_cvar("parachute_cost", "0")
  26.             pPayback = register_cvar("parachute_payback", "75")
  27.      
  28.             register_concmd("amx_parachute", "admin_give_parachute", PARACHUTE_LEVEL, "<nick, #userid or @team>" )
  29.         }
  30.      
  31.         register_clcmd("say", "HandleSay")
  32.         register_clcmd("say_team", "HandleSay")
  33.      
  34.         register_event("ResetHUD", "newSpawn", "be")
  35.         register_event("DeathMsg", "death_event", "a")
  36.      
  37.         //Setup jtp10181 CVAR
  38.         new cvarString[256], shortName[16]
  39.         copy(shortName,15,"chute")
  40.      
  41.         register_cvar("jtp10181","",FCVAR_SERVER|FCVAR_SPONLY)
  42.         get_cvar_string("jtp10181",cvarString,255)
  43.      
  44.         if (strlen(cvarString) == 0) {
  45.             formatex(cvarString,255,shortName)
  46.             set_cvar_string("jtp10181",cvarString)
  47.         }
  48.         else if (contain(cvarString,shortName) == -1) {
  49.             format(cvarString,255,"%s,%s",cvarString, shortName)
  50.             set_cvar_string("jtp10181",cvarString)
  51.         }
  52.     }
  53.      
  54.     public plugin_natives()
  55.     {
  56.         set_module_filter("module_filter")
  57.         set_native_filter("native_filter")
  58.     }
  59.      
  60.     public module_filter(const module[])
  61.     {
  62.         if (!cstrike_running() && equali(module, "cstrike")) {
  63.             return PLUGIN_HANDLED
  64.         }
  65.      
  66.         return PLUGIN_CONTINUE
  67.     }
  68.      
  69.     public native_filter(const name[], index, trap)
  70.     {
  71.         if (!trap) return PLUGIN_HANDLED
  72.      
  73.         return PLUGIN_CONTINUE
  74.     }
  75.      
  76.     public plugin_precache()
  77.     {
  78.         precache_model("models/parachute.mdl")
  79.     }
  80.      
  81.     public client_connect(id)
  82.     {
  83.         parachute_reset(id)
  84.     }
  85.      
  86.     public client_disconnect(id)
  87.     {
  88.         parachute_reset(id)
  89.     }
  90.      
  91.     public death_event()
  92.     {
  93.         new id = read_data(2)
  94.         parachute_reset(id)
  95.     }
  96.      
  97.     parachute_reset(id)
  98.     {
  99.         if(para_ent[id] > 0) {
  100.             if (is_valid_ent(para_ent[id])) {
  101.                 remove_entity(para_ent[id])
  102.             }
  103.         }
  104.      
  105.         if (is_user_alive(id)) set_user_gravity(id, 1.0)
  106.      
  107.         has_parachute[id] = false
  108.         para_ent[id] = 0
  109.     }
  110.      
  111.     public newSpawn(id)
  112.     {
  113.         if(para_ent[id] > 0) {
  114.             remove_entity(para_ent[id])
  115.             set_user_gravity(id, 1.0)
  116.             para_ent[id] = 0
  117.         }
  118.      
  119.         if (!gCStrike || access(id,PARACHUTE_LEVEL) || get_pcvar_num(pCost) <= 0) {
  120.             has_parachute[id] = true
  121.             //set_view(id, CAMERA_3RDPERSON)
  122.         }
  123.     }
  124.      
  125.     public HandleSay(id)
  126.     {
  127.         if(!is_user_connected(id)) return PLUGIN_CONTINUE
  128.      
  129.         new args[128]
  130.         read_args(args, 127)
  131.         remove_quotes(args)
  132.      
  133.         if (gCStrike) {
  134.             if (equali(args, "buy_parachute")) {
  135.                 buy_parachute(id)
  136.                 return PLUGIN_HANDLED
  137.             }
  138.             else if (equali(args, "sell_parachute")) {
  139.                 sell_parachute(id)
  140.                 return PLUGIN_HANDLED
  141.             }
  142.             else if (containi(args, "give_parachute") == 0) {
  143.                 give_parachute(id,args[15])
  144.                 return PLUGIN_HANDLED
  145.             }
  146.         }
  147.      
  148.         if (containi(args, "parachute") != -1) {
  149.             if (gCStrike) client_print(id, print_chat, "[AMXX] Ejt�erny� parancsok: buy_parachute, sell_parachute, give_parachute")
  150.             client_print(id, print_chat, "[AMXX] Az ejt�erny� haszn�lat�hoz nyomd meg az E bet�t")
  151.         }
  152.      
  153.         return PLUGIN_CONTINUE
  154.     }
  155.      
  156.     public buy_parachute(id)
  157.     {
  158.         if (!gCStrike) return PLUGIN_CONTINUE
  159.         if (!is_user_connected(id)) return PLUGIN_CONTINUE
  160.      
  161.         if (!get_pcvar_num(pEnabled)) {
  162.             client_print(id, print_chat, "[AMXX] Ejt�erny� plugin kikapcsolva")
  163.             return PLUGIN_HANDLED
  164.         }
  165.      
  166.         if (has_parachute[id]) {
  167.             client_print(id, print_chat, "[AMXX] Neked m�r van ejt�erny�d")
  168.             return PLUGIN_HANDLED
  169.         }
  170.      
  171.         new money = cs_get_user_money(id)
  172.         new cost = get_pcvar_num(pCost)
  173.      
  174.         if (money < cost) {
  175.             client_print(id, print_chat, "[AMXX] Nincs el�g p�nzed az ejt�erny� v�s�rl�s�hoz - Costs $%i", cost)
  176.             return PLUGIN_HANDLED
  177.         }
  178.      
  179.         cs_set_user_money(id, money - cost)
  180.         client_print(id, print_chat, "[AMXX] Vett�l egy ejt�erny�t Haszn�lathoz nyomd meg az E bet�t")
  181.         has_parachute[id] = true
  182.      
  183.         return PLUGIN_HANDLED
  184.     }
  185.      
  186.     public sell_parachute(id)
  187.     {
  188.         if (!gCStrike) return PLUGIN_CONTINUE
  189.         if (!is_user_connected(id)) return PLUGIN_CONTINUE
  190.      
  191.         if (!get_pcvar_num(pEnabled)) {
  192.             client_print(id, print_chat, "[AMXX] Ejt�erny� plugin kikapcsolva")
  193.             return PLUGIN_HANDLED
  194.         }
  195.      
  196.         if (!has_parachute[id]) {
  197.             client_print(id, print_chat, "[AMXX] Neked nincs ejt�erny�d ez�rt nem tudod eladni")
  198.             return PLUGIN_HANDLED
  199.         }
  200.      
  201.         if (access(id,PARACHUTE_LEVEL)) {
  202.             client_print(id, print_chat, "[AMXX] Te nem adhatod el az admin �ltal adott ingyen ejt�erny�t!")
  203.             return PLUGIN_HANDLED
  204.         }
  205.      
  206.         parachute_reset(id)
  207.      
  208.         new money = cs_get_user_money(id)
  209.         new cost = get_pcvar_num(pCost)
  210.      
  211.         new sellamt = floatround(cost * (get_pcvar_num(pPayback) / 100.0))
  212.         cs_set_user_money(id, money + sellamt)
  213.      
  214.         client_print(id, print_chat, "[AMX] Eladtad a haszn�lt ejt�erny�d ennyi�rt: $%d", sellamt)
  215.      
  216.         return PLUGIN_CONTINUE
  217.     }
  218.      
  219.     public give_parachute(id,args[])
  220.     {
  221.         if (!gCStrike) return PLUGIN_CONTINUE
  222.         if (!is_user_connected(id)) return PLUGIN_CONTINUE
  223.      
  224.         if (!get_pcvar_num(pEnabled)) {
  225.             client_print(id, print_chat, "[AMXX] Ejt�erny� plugin bekapcsolva")
  226.             return PLUGIN_HANDLED
  227.         }
  228.      
  229.         if (!has_parachute[id]) {
  230.             client_print(id, print_chat, "[AMXX] Te nem tudod haszn�lni az ejt�erny�d mert nincs.")
  231.             return PLUGIN_HANDLED
  232.         }
  233.      
  234.         new player = cmd_target(id, args, 4)
  235.         if (!player) return PLUGIN_HANDLED
  236.      
  237.         new id_name[32], pl_name[32]
  238.         get_user_name(id, id_name, 31)
  239.         get_user_name(player, pl_name, 31)
  240.      
  241.         if(has_parachute[player]) {
  242.             client_print(id, print_chat, "[AMXX] %s m�r van ejt�erny�je.", pl_name)
  243.             return PLUGIN_HANDLED
  244.         }
  245.      
  246.         parachute_reset(id)
  247.         has_parachute[player] = true
  248.      
  249.         client_print(id, print_chat, "[AMXX] Adt�l ejt�erny�t neki : %s.", pl_name)
  250.         client_print(player, print_chat, "[AMXX] %s haszn�lja az ejt�erny�t amit te adt�l neki.", id_name)
  251.      
  252.         return PLUGIN_HANDLED
  253.     }
  254.      
  255.     public admin_give_parachute(id, level, cid) {
  256.      
  257.         if (!gCStrike) return PLUGIN_CONTINUE
  258.      
  259.         if(!cmd_access(id,level,cid,2)) return PLUGIN_HANDLED
  260.      
  261.         if (!get_pcvar_num(pEnabled)) {
  262.             client_print(id, print_chat, "[AMXX] ejt�erny� plugin kikapcsolva")
  263.             return PLUGIN_HANDLED
  264.         }
  265.      
  266.         new arg[32], name[32], name2[32], authid[35], authid2[35]
  267.         read_argv(1,arg,31)
  268.         get_user_name(id,name,31)
  269.         get_user_authid(id,authid,34)
  270.      
  271.         if (arg[0]=='@'){
  272.             new players[32], inum
  273.             if (equali("T",arg[1]))     copy(arg[1],31,"TERRORIST")
  274.             if (equali("ALL",arg[1]))   get_players(players,inum)
  275.             else                        get_players(players,inum,"e",arg[1])
  276.      
  277.             if (inum == 0) {
  278.                 console_print(id,"No clients in such team")
  279.                 return PLUGIN_HANDLED
  280.             }
  281.      
  282.             for(new a = 0; a < inum; a++) {
  283.                 has_parachute[players[a]] = true
  284.             }
  285.      
  286.             switch(get_cvar_num("amx_show_activity"))   {
  287.                 case 2: client_print(0,print_chat,"ADMIN %s: adott egy ejt�erny�t ^"%s^" playernek",name,arg[1])
  288.                 case 1: client_print(0,print_chat,"ADMIN: Adott egy ejt�erny�t ^"%s^" playernek",arg[1])
  289.             }
  290.      
  291.             console_print(id,"[AMXX] Te adt�l egy ejt�erny�t ^"%s^" playernek",arg[1])
  292.             log_amx("^"%s<%d><%s><>^" adt�l ejt�erny�t ^"%s^"", name,get_user_userid(id),authid,arg[1])
  293.         }
  294.         else {
  295.      
  296.             new player = cmd_target(id,arg,6)
  297.             if (!player) return PLUGIN_HANDLED
  298.      
  299.             has_parachute[player] = true
  300.      
  301.             get_user_name(player,name2,31)
  302.             get_user_authid(player,authid2,34)
  303.      
  304.             switch(get_cvar_num("amx_show_activity")) {
  305.                 case 2: client_print(0,print_chat,"ADMIN %s: adt�l ejt�erny�t ^"%s^"",name,name2)
  306.                 case 1: client_print(0,print_chat,"ADMIN: adt�l ejt�erny�t ^"%s^"",name2)
  307.             }
  308.      
  309.             console_print(id,"[AMXX] te adt�l ejt�erny�t ^"%s^"", name2)
  310.             log_amx("^"%s<%d><%s><>^" adt�l ejt�erny�t ^"%s<%d><%s><>^"", name,get_user_userid(id),authid,name2,get_user_userid(player),authid2)
  311.         }
  312.         return PLUGIN_HANDLED
  313.     }
  314.      
  315.     public client_PreThink(id)
  316.     {
  317.         //parachute.mdl animation information
  318.         //0 - deploy - 84 frames
  319.         //1 - idle - 39 frames
  320.         //2 - detach - 29 frames
  321.      
  322.         if (!get_pcvar_num(pEnabled)) return
  323.         if (!is_user_alive(id) || !has_parachute[id]) return
  324.      
  325.         new Float:fallspeed = get_pcvar_float(pFallSpeed) * -1.0
  326.         new Float:frame
  327.      
  328.         new button = get_user_button(id)
  329.         new oldbutton = get_user_oldbutton(id)
  330.         new flags = get_entity_flags(id)
  331.      
  332.         if (para_ent[id] > 0 && (flags & FL_ONGROUND)) {
  333.      
  334.             if (get_pcvar_num(pDetach)) {
  335.      
  336.                 if (get_user_gravity(id) == 0.1) set_user_gravity(id, 1.0)
  337.      
  338.                 if (entity_get_int(para_ent[id],EV_INT_sequence) != 2) {
  339.                     entity_set_int(para_ent[id], EV_INT_sequence, 2)
  340.                     entity_set_int(para_ent[id], EV_INT_gaitsequence, 1)
  341.                     entity_set_float(para_ent[id], EV_FL_frame, 0.0)
  342.                     entity_set_float(para_ent[id], EV_FL_fuser1, 0.0)
  343.                     entity_set_float(para_ent[id], EV_FL_animtime, 0.0)
  344.                     entity_set_float(para_ent[id], EV_FL_framerate, 0.0)
  345.                     return
  346.                 }
  347.      
  348.                 frame = entity_get_float(para_ent[id],EV_FL_fuser1) + 2.0
  349.                 entity_set_float(para_ent[id],EV_FL_fuser1,frame)
  350.                 entity_set_float(para_ent[id],EV_FL_frame,frame)
  351.      
  352.                 if (frame > 254.0) {
  353.                     remove_entity(para_ent[id])
  354.                     para_ent[id] = 0
  355.                 }
  356.             }
  357.             else {
  358.                 remove_entity(para_ent[id])
  359.                 set_user_gravity(id, 1.0)
  360.                 para_ent[id] = 0
  361.             }
  362.      
  363.             return
  364.         }
  365.      
  366.         if (button & IN_USE) {
  367.      
  368.             new Float:velocity[3]
  369.             entity_get_vector(id, EV_VEC_velocity, velocity)
  370.      
  371.             if (velocity[2] < 0.0) {
  372.      
  373.                 if(para_ent[id] <= 0) {
  374.                     para_ent[id] = create_entity("info_target")
  375.                     if(para_ent[id] > 0) {
  376.                         entity_set_string(para_ent[id],EV_SZ_classname,"parachute")
  377.                         entity_set_edict(para_ent[id], EV_ENT_aiment, id)
  378.                         entity_set_edict(para_ent[id], EV_ENT_owner, id)
  379.                         entity_set_int(para_ent[id], EV_INT_movetype, MOVETYPE_FOLLOW)
  380.                         entity_set_model(para_ent[id], "models/parachute.mdl")
  381.                         entity_set_int(para_ent[id], EV_INT_sequence, 0)
  382.                         entity_set_int(para_ent[id], EV_INT_gaitsequence, 1)
  383.                         entity_set_float(para_ent[id], EV_FL_frame, 0.0)
  384.                         entity_set_float(para_ent[id], EV_FL_fuser1, 0.0)
  385.                     }
  386.                 }
  387.      
  388.                 if (para_ent[id] > 0) {
  389.      
  390.                     entity_set_int(id, EV_INT_sequence, 3)
  391.                     entity_set_int(id, EV_INT_gaitsequence, 1)
  392.                     entity_set_float(id, EV_FL_frame, 1.0)
  393.                     entity_set_float(id, EV_FL_framerate, 1.0)
  394.                     set_user_gravity(id, 0.1)
  395.      
  396.                     velocity[2] = (velocity[2] + 40.0 < fallspeed) ? velocity[2] + 40.0 : fallspeed
  397.                     entity_set_vector(id, EV_VEC_velocity, velocity)
  398.      
  399.                     if (entity_get_int(para_ent[id],EV_INT_sequence) == 0) {
  400.      
  401.                         frame = entity_get_float(para_ent[id],EV_FL_fuser1) + 1.0
  402.                         entity_set_float(para_ent[id],EV_FL_fuser1,frame)
  403.                         entity_set_float(para_ent[id],EV_FL_frame,frame)
  404.      
  405.                         if (frame > 100.0) {
  406.                             entity_set_float(para_ent[id], EV_FL_animtime, 0.0)
  407.                             entity_set_float(para_ent[id], EV_FL_framerate, 0.4)
  408.                             entity_set_int(para_ent[id], EV_INT_sequence, 1)
  409.                             entity_set_int(para_ent[id], EV_INT_gaitsequence, 1)
  410.                             entity_set_float(para_ent[id], EV_FL_frame, 0.0)
  411.                             entity_set_float(para_ent[id], EV_FL_fuser1, 0.0)
  412.                         }
  413.                     }
  414.                 }
  415.             }
  416.             else if (para_ent[id] > 0) {
  417.                 remove_entity(para_ent[id])
  418.                 set_user_gravity(id, 1.0)
  419.                 para_ent[id] = 0
  420.             }
  421.         }
  422.         else if ((oldbutton & IN_USE) && para_ent[id] > 0 ) {
  423.             remove_entity(para_ent[id])
  424.             set_user_gravity(id, 1.0)
  425.             para_ent[id] = 0
  426.         }
  427.     }

_________________
 ! Moderátori megjegyzés (kiki):
Kérlek csekkold az aláírásra vonatkozó szabályokat!


A hozzászólást 1 alkalommal szerkesztették, utoljára The Peace 2016.03.24. 09:00-kor.

Ők köszönték meg The Peace nek ezt a hozzászólást: titkos (2016.03.24. 08:54)
  Népszerűség: 2.27%


Hozzászólás jelentése
Vissza a tetejére
   
 Hozzászólás témája: Re: Ejtőernyő minden játékosnak
HozzászólásElküldve:2016.03.24. 09:00 
Offline
Tiszteletbeli
Avatar

Csatlakozott:2014.09.08. 22:21
Hozzászólások:3014
Megköszönt másnak: 96 alkalommal
Megköszönték neki: 555 alkalommal
Igen inkább használd a prethinkest azt írd be :D Istenem

_________________
GitHub - mforce


Hozzászólás jelentése
Vissza a tetejére
   
 Hozzászólás témája: Re: Ejtőernyő minden játékosnak
HozzászólásElküldve:2016.03.24. 09:56 
Offline
Tiszteletbeli
Avatar

Csatlakozott:2014.09.08. 22:21
Hozzászólások:3014
Megköszönt másnak: 96 alkalommal
Megköszönték neki: 555 alkalommal
Attól, hogy módosítod a hozzászólásod még baromság marad.
Minek fusson le milliárdszor? public client_PreThink(id)

Ha mehet csak gomblenyomáskor is?
(amit beillesztettem fentebb)

_________________
GitHub - mforce


Hozzászólás jelentése
Vissza a tetejére
   
Hozzászólások megjelenítése: Rendezés 
Új téma nyitása Hozzászólás a témához  [5 hozzászólás ] 


Ki van itt

Jelenlévő fórumozók: nincs regisztrált felhasználó valamint 22 vendég


Nyithatsz új témákat ebben a fórumban.
Válaszolhatsz egy témára ebben a fórumban.
Nem szerkesztheted a hozzászólásaidat ebben a fórumban.
Nem törölheted a hozzászólásaidat ebben a fórumban.
Nem küldhetsz csatolmányokat ebben a fórumban.

Keresés:
Ugrás:  
Powered by phpBB® Forum Software © phpBB Limited
Magyar fordítás © Magyar phpBB Közösség
Portal: Kiss Portal Extension © Michael O'Toole