System Damage

Jesus4Lyf

Good Idea™
Damage​
Version 1.0.5​

Requirements:
- Jass NewGen
- AIDS
- Event

JASS:
//  
//      ___   _     __  __   _   ___  ____    _______________________________
//     |   \ /_\   /  |/  | /_\ /  _\|  __|   ||      D E A L   I T ,      ||
//     | |) / _ \ / / | / |/ _ \| |/||  __|   ||    D E T E C T   I T ,    ||
//     |___/_/ \_/_/|__/|_|_/ \_\___/|____|   ||     B L O C K   I T .     ||
//                            By Jesus4Lyf    ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//                                                                    v 1.0.5
//      What is Damage?
//     ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//          Damage is a damage dealing, detection and blocking system. It implements
//          all such functionality. It also provides a means to detect what type
//          of damage was dealt, so long as all damage in your map is dealt using
//          this system's deal damage functions (except for basic attacks).
//
//          It is completely recursively defined, meaning if you deal damage on
//          taking damage, the type detection and other features like blocking
//          will not malfunction.
//          
//      How to implement?
//     ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//          Create a new trigger object called Damage, go to 'Edit -> Convert to
//          Custom Text', and replace everything that's there with this script.
//
//          At the top of the script, there is a '//! external ObjectMerger' line.
//          Save your map, close your map, reopen your map, and then comment out this
//          line. Damage is now implemented. This line creates a dummy ability used
//          in the system in some circumstances with damage blocking.
//
//      Functions:
//     ¯¯¯¯¯¯¯¯¯¯¯¯
//          function Damage_RegisterEvent takes trigger whichTrigger returns nothing
//              - This registers a special "any unit takes damage" event.
//              - This event supports dynamic trigger use.
//              - Only triggers registered on this event may block damage.
//              - Only fires when the damage dealt is not 0.
//
//          function Damage_RegisterZeroEvent takes trigger whichTrigger returns nothing
//              - The same as Damage_RegisterEvent, but for only zero damage events
//                (which are excluded from Damage_RegisterEvent).
//              - Note that getting the damage type may be unreliable, since spells
//                like faerie fire trigger 0 damage, but it will count as an attack.
//
//          function Damage_GetType takes nothing returns damagetype
//              - This will get the type of damage dealt, like an event response,
//                for when using a unit takes damage event (or the special event above).
//
//          function Damage_IsPhysical takes nothing returns boolean
//          function Damage_IsSpell takes nothing returns boolean
//          function Damage_IsPure takes nothing returns boolean
//              - Wrappers to simply check if Damage_GetType is certain types.
//
//          function Damage_IsAttack takes nothing returns boolean
//              - Checks if the damage is from a physical attack (so you can deal
//                physical damage without it being registered as an actual attack).
//
//          function Damage_Block takes real amount returns nothing
//          function Damage_BlockAll takes nothing returns nothing
//              - For use only with Damage_RegisterEvent.
//              - Blocks 'amount' of the damage dealt.
//              - Multiple blocks at once work correctly.
//              - Blocking more than 100% of the damage will block 100% instead.
//              - Damage_BlockAll blocks 100% of the damage being dealt.
//
//          function Damage_EnableEvent takes boolean enable returns nothing
//              - For disabling and re-enabling the special event.
//              - Use it to deal damage which you do not want to be detected by
//                the special event.
//
//          function UnitDamageTargetEx takes lots of things returns boolean
//              - Replaces UnitDamageTarget in your map, with the same arguments.
//
//          function Damage_Physical takes unit source, unit target, real amount,
//            attacktype whichType, boolean attack, boolean ranged returns boolean
//              - A clean wrapper for physical damage.
//              - 'attack' determines if this is to be treated as a real physical
//                attack or just physical type damage.
//              - 'ranged' determines if this is to be treated as a ranged or melee
//                attack.
//
//          function Damage_Spell takes unit source, unit target, real amount returns boolean
//              - A clean wrapper for spell damage.
//
//          function Damage_Pure takes unit source, unit target, real amount returns boolean
//              - A clean wrapper for pure type damage (universal type, 100% damage).
//          
//      Thanks:
//     ¯¯¯¯¯¯¯¯¯
//          - Romek, for helping me find a better way to think about damage blocking.
//
library Damage uses AIDS, Event
    //============================================================
    //! external ObjectMerger w3a AIlz dprv anam "Life Bonus" ansf "(Damage System)" Ilif 1 500000 aite 0
    globals
        private constant integer LIFE_BONUS_ABIL='dprv'
    endglobals
    
    //============================================================
    globals
        private Event OnDamageEvent
        private Event OnZeroDamageEvent
        private boolean EventEnabled=true
    endglobals
    
    public function RegisterEvent takes trigger whichTrigger returns nothing
        call OnDamageEvent.register(whichTrigger)
    endfunction
    
    public function RegisterZeroEvent takes trigger whichTrigger returns nothing
        call OnZeroDamageEvent.register(whichTrigger)
    endfunction
    
    public function EnableEvent takes boolean enable returns nothing
        set EventEnabled=enable
    endfunction
    
    //============================================================
    globals
        private integer TypeStackLevel=0
        private damagetype array TypeStackValue
        private boolean array TypeStackAttack
        private real array ToBlock
    endglobals
    
    public function GetType takes nothing returns damagetype
        return TypeStackValue[TypeStackLevel]
    endfunction
    
    public function IsAttack takes nothing returns boolean
        return TypeStackAttack[TypeStackLevel]
    endfunction
    
    public function Block takes real amount returns nothing
        set ToBlock[TypeStackLevel]=ToBlock[TypeStackLevel]+amount
    endfunction
    
    public function BlockAll takes nothing returns nothing
        set ToBlock[TypeStackLevel]=ToBlock[TypeStackLevel]+GetEventDamage()
    endfunction
    
    //============================================================
    globals
        private integer BlockNum=0
        private unit array BlockUnit
        private real array BlockUnitLife
        private real array BlockRedamage
        private unit array BlockDamageSource
        
        private timer BlockTimer=CreateTimer()
    endglobals
    
    //============================================================
    globals
        private unit array RemoveBoosted
        private integer RemoveBoostedMax=0
        
        private timer RemoveBoostedTimer=CreateTimer()
    endglobals
    
    globals//locals
        private real BoostedLifeTemp
        private unit BoostedLifeUnit
    endglobals
    private function RemoveBoostedTimerFunc takes nothing returns nothing
        loop
            exitwhen RemoveBoostedMax==0
            set BoostedLifeUnit=RemoveBoosted[RemoveBoostedMax]
            set BoostedLifeTemp=GetWidgetLife(BoostedLifeUnit)
            call UnitRemoveAbility(BoostedLifeUnit,LIFE_BONUS_ABIL)
            if BoostedLifeTemp>0.405 then
                call SetWidgetLife(BoostedLifeUnit,BoostedLifeTemp)
            endif
            set RemoveBoostedMax=RemoveBoostedMax-1
        endloop
    endfunction
    
    //============================================================
    private keyword Detector // Darn, I actually had to do this. XD
    globals//locals
        private unit ForUnit
        private real NextHealth
    endglobals
    private function OnDamageActions takes nothing returns boolean
        if EventEnabled then
            if GetEventDamage()==0. then
                call OnZeroDamageEvent.fire()
            else
                call OnDamageEvent.fire()
            endif
            
            if ToBlock[TypeStackLevel]!=0. then
                //====================================================
                // Blocking
                set ForUnit=GetTriggerUnit()
                
                set NextHealth=GetEventDamage()
                if ToBlock[TypeStackLevel]>=NextHealth then
                    set NextHealth=GetWidgetLife(ForUnit)+NextHealth
                else
                    set NextHealth=GetWidgetLife(ForUnit)+ToBlock[TypeStackLevel]
                endif
                
                call SetWidgetLife(ForUnit,NextHealth)
                if GetWidgetLife(ForUnit)<NextHealth then
                    // NextHealth is over max health.
                    call UnitAddAbility(ForUnit,LIFE_BONUS_ABIL)
                    call SetWidgetLife(ForUnit,NextHealth)
                    
                    set RemoveBoostedMax=RemoveBoostedMax+1
                    set RemoveBoosted[RemoveBoostedMax]=ForUnit
                    call ResumeTimer(RemoveBoostedTimer)
                endif
                //====================================================
                set ToBlock[TypeStackLevel]=0.
            endif
        endif
        return false
    endfunction
    
    //============================================================
    function UnitDamageTargetEx takes unit whichUnit, widget target, real amount, boolean attack, boolean ranged, attacktype attackType, damagetype damageType, weapontype weaponType returns boolean
        local boolean result
        set TypeStackLevel=TypeStackLevel+1
        set TypeStackValue[TypeStackLevel]=damageType
        set TypeStackAttack[TypeStackLevel]=attack
        set result=UnitDamageTarget(whichUnit,target,amount,attack,ranged,attackType,damageType,weaponType)
        set TypeStackLevel=TypeStackLevel-1
        return result
    endfunction
    //! textmacro Damage__DealTypeFunc takes NAME, TYPE
        public function $NAME$ takes unit source, unit target, real amount returns boolean
            return UnitDamageTargetEx(source,target,amount,false,false,ATTACK_TYPE_NORMAL,$TYPE$,WEAPON_TYPE_WHOKNOWS)
        endfunction
        public function Is$NAME$ takes nothing returns boolean
            return GetType()==$TYPE$
        endfunction
    //! endtextmacro
    
    //! runtextmacro Damage__DealTypeFunc("Pure","DAMAGE_TYPE_UNIVERSAL")
    //! runtextmacro Damage__DealTypeFunc("Spell","DAMAGE_TYPE_MAGIC")
    
    // Uses different stuff, but works much the same way.
    public function Physical takes unit source, unit target, real amount, attacktype whichType, boolean attack, boolean ranged returns boolean
        return UnitDamageTargetEx(source,target,amount,attack,ranged,whichType,DAMAGE_TYPE_NORMAL,WEAPON_TYPE_WHOKNOWS)
    endfunction
    public function IsPhysical takes nothing returns boolean
        return GetType()==DAMAGE_TYPE_NORMAL
    endfunction
    
    //============================================================
    private struct Detector extends array // Uses AIDS.
        //! runtextmacro AIDS()
        
        private static conditionfunc ACTIONS_COND
        
        private trigger t
        
        private method AIDS_onCreate takes nothing returns nothing
            set this.t=CreateTrigger()
            call TriggerAddCondition(this.t,thistype.ACTIONS_COND)
            call TriggerRegisterUnitEvent(this.t,this.unit,EVENT_UNIT_DAMAGED)
        endmethod
        
        private method AIDS_onDestroy takes nothing returns nothing
            call DestroyTrigger(this.t)
        endmethod
        
        private static method AIDS_onInit takes nothing returns nothing
            set thistype.ACTIONS_COND=Condition(function OnDamageActions)
        endmethod
    endstruct
    
    //============================================================
    private module InitModule
        private static method onInit takes nothing returns nothing
            local unit abilpreload=CreateUnit(Player(15),'uloc',0,0,0)
            call UnitAddAbility(abilpreload,LIFE_BONUS_ABIL)
            call RemoveUnit(abilpreload)
            set abilpreload=null
            
            set OnDamageEvent=Event.create()
            set OnZeroDamageEvent=Event.create()
            set TypeStackValue[TypeStackLevel]=DAMAGE_TYPE_NORMAL
            set TypeStackAttack[TypeStackLevel]=true
            call TimerStart(RemoveBoostedTimer,0.0,false,function RemoveBoostedTimerFunc)
        endmethod
    endmodule
    private struct InitStruct extends array
        implement InitModule
    endstruct
endlibrary

TESH Highlighting:
JASS:
//
//      _____ ___ ___ _   _ 
//     |_   _| __/ __| |_| |
//       | | | _|\__ \  _  |
//       |_| |___|___/_| |_| Highlighting:
//                            - Damage by Jesus4Lyf.
//
//      What is this?
//     ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//          Implementing this allows the TESH syntax highlighter in Jass NewGen
//          to highlight the syntax for this system (and autocomplete the functions).
//          
//      How to import?
//     ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//          Copy this into a blank text file, and save it at:
//          <Your Jass NewGen folder>\tesh\includes\Damage.j
//
function Damage_RegisterEvent takes trigger whichTrigger returns nothing
    // Registers an event: Any unit is damaged.
endfunction
function Damage_RegisterZeroEvent takes trigger whichTrigger returns nothing
    // Registers an event: Any unit is dealt zero damage.
endfunction
function Damage_GetType takes nothing returns damagetype
    // Requires all damage in the map (except basic attacks) to be dealt with one of:
    //  - UnitDamageTargetEx
    //  - Damage_Physical
    //  - Damage_Spell
    //  - Damage_Pure
endfunction
function Damage_IsPhysical takes nothing returns boolean
    // Requires all damage in the map (except basic attacks) to be dealt with one of:
    //  - UnitDamageTargetEx
    //  - Damage_Physical
    //  - Damage_Spell
    //  - Damage_Pure
endfunction
function Damage_IsSpell takes nothing returns boolean
    // Requires all damage in the map (except basic attacks) to be dealt with one of:
    //  - UnitDamageTargetEx
    //  - Damage_Physical
    //  - Damage_Spell
    //  - Damage_Pure
endfunction
function Damage_IsPure takes nothing returns boolean
    // Requires all damage in the map (except basic attacks) to be dealt with one of:
    //  - UnitDamageTargetEx
    //  - Damage_Physical
    //  - Damage_Spell
    //  - Damage_Pure
endfunction
function Damage_IsAttack takes nothing returns boolean
    // Requires all damage in the map (except basic attacks) to be dealt with one of:
    //  - UnitDamageTargetEx
    //  - Damage_Physical
    //  - Damage_Spell
    //  - Damage_Pure
endfunction
function Damage_Block takes real amount returns nothing
    // Must only be used from a trigger with the event from:
    //  - Damage_RegisterEvent
endfunction
function Damage_BlockAll takes nothing returns nothing
    // Must only be used from a trigger with the event from:
    //  - Damage_RegisterEvent
endfunction
function Damage_EnableEvent takes boolean enable returns nothing
endfunction
function UnitDamageTargetEx takes unit source, widget target, real amount, boolean attack, boolean ranged, attacktype attackType, damagetype damageType, weapontype weaponType returns boolean
    // The same parameters as the native UnitDamageTarget, but allows type detection.
endfunction
function Damage_Physical takes unit source, unit target, real amount, attacktype whichType, boolean attack, boolean ranged returns boolean
    // "attack" is whether or not the damage is an attack.
    // "ranged" is whether or not the damage is a ranged attack (set "attack" to true also).
endfunction
function Damage_Spell takes unit source, unit target, real amount returns boolean
endfunction
function Damage_Pure takes unit source, unit target, real amount returns boolean
endfunction

"Damage" is a full damage system for any map. It doesn't leak events internally, supports damage-on-damage functions without bugging (so it supports recursion), etc.

The main features are:
  • An AnyUnitDamaged event (which supports dynamic triggers just fine)
  • Instant damage blocking (only for triggers registered on the above event)
  • Get dealt damage type (as long as all damage is dealt with the functions from this system - except for basic attacks - which means the map's spells must damage through this system and not use damage from things like frost nova... so this feature is only for maps where the map is built to run on this system).
Demo map is a simple demonstration of stopping everything from dealing damage on the map. :)

Updates:
- Version 1.0.5: Added Damage_RegisterZeroEvent(trig).
- Version 1.0.4: Changed initialiser to module/struct to make it fire first.
- Version 1.0.3: Stopped detection of 0.0 damage events.
- Version 1.0.2: Added Damage_IsPure(), Damage_IsSpell(), Damage_IsPhysical() and Damage_IsAttack() (so damage can be physical but not count as an attack).
- Version 1.0.1: Changed the syntax from EnableEvent and DisableEvent to EnableEvent(boolean).
- Version 1.0.0: Release.
 

Attachments

Renendaru

(Evol)ution is nothing without love.
Finally you submit this, once you told me about it on the chat I couldn't wait. :thup:
Looks good.
 

Viikuna

No Marlo no game.
It would be better to use use one trigger with multiple events to detect damage and recycle units to avoid event leaks, but I guess this is ok too...
 

Troll-Brain

You can change this now in User CP.
Recycle units is not easy, and i guess a good universal system can't be done without any Warcraft III 3rd party tools like GMSI .
If you want a good recycle system you should use many instant morph abilities and there are many side effect to play around them ...
 

Steel

Software Engineer
It would be a good idea to benchmark your system with a small, medium, large and very large number of units at once. I know from experience that having too many units in a system like this that has too many function calls or conditions can cause noticeable slowdowns. You may want to examine this because the slowdowns can get to the point where players can see what is happening and you always want to mask what your system is doing.
 

T.s.e

Wish I was old and a little sentimental
Who cares if this is one hundredth of a milisecond slower than another system, as long as it's easily used and actually works, that's enough for me.
 

Hatebreeder

So many apples
JASS:
    globals//locals
        private real BoostedLifeTemp
        private unit BoostedLifeUnit
    endglobals


What do you mean with Globals/Locals?
I don't see how they behave like locals, actually... Since they ARE globals, or?
I havn't used vJASS for a while now...
Many things changed :X
 

Romek

Super Moderator
Staff member
He just uses global variables instead of local variables in the instant functions.
They're about 0.000000000001 nanoseconds faster, and don't need nulling. :p
 

Jesus4Lyf

Good Idea™
Yay, replies.

>It would be better to use use one
I'm not going to release a system that assumes players took the ridiculous measure to recycle units, and if they did, I believe the triggers in this would never actually get destroyed, so this supports unit recycling just fine anyway. :thup:

>It would be a good idea to benchmark your system
Benchmark -what- part of my system? Every single part of this system is O(1) complexity (in relation to its interface) so nothing should slow down anything at all. I don't know what you're talking about with units slowing things down, can you give me an example of why it might slow?

Looking at that alternative (no offense to the author, but these are the things I noticed):
IDDS:
  • Doesn't work: If you have a damage on damage action, the damage type and stuff screws up completely. It isn't recursively defined, which is VITAL to a system like this actually being reliable. I would never use IDDS for that reason - it breaks.
  • Uses TriggerUnregisterDamageEvent which isn't a nice interface, so this isn't behaving like a real event anyway, because you can't just destroy triggers.
  • Damage type is an integer (how is that intuitive?) which is actually one of the reasons I wrote this.
  • Uses H2I (Table) which will break on 1.24, etc. I do not want any of my maps to break when new patches come out. Purely personal choice, but I wrote this system out of necessity.
  • Has an O(n) complexity trigger sorting mechanism for some reason.
That was just at a quick glance.

>Who cares if this is one hundredth of a milisecond slower than another system
No, this system should be faster than other systems which try to accomplish the same thing. AND actually work. :) :thup:
 

Renendaru

(Evol)ution is nothing without love.
I've updated most triggers to use Damage and most things are running faster/better than they did. For example my trigger used to be delayed since I had to use a wait in a trigger for my old system. But now, it works perfectly without delays.
 

lep

Active Member
>Uses H2I (Table) which will break on 1.24, etc. I do not want any of my maps to break >when new patches come out. Purely personal choice, but I wrote this system out of >necessity.

Hell, no. Since he uses Table as a abstraction everyone using IDDS just have to update
Table.

>Has an O(n) complexity trigger sorting mechanism for some reason.

An O(n) sorting algorithm wouls be awesome....


>>Who cares if this is one hundredth of a milisecond slower than another system
>No, this system should be faster than other systems which try to accomplish the >same thing. AND actually work. :) :thup:

Ahh, that kind of mapper...
And don't use Dusks System but i would prefer his to this, because those priorities are actualy quite nice.
 

Jesus4Lyf

Good Idea™
>Hell, no. Since he uses Table as a abstraction everyone using IDDS just have to update Table.
Actually, he incorporated his own H2I and didn't use HandleTables. Besides (and this is irrelevant), even using HandleTables in your map will make it break for 1.24. I want my 1.23 RELEASE to work on 1.24.

>An O(n) sorting algorithm wouls be awesome....
Actually, his is O(n^2).

>Ahh, that kind of mapper...
Yes, that kind of mapper. One who makes things which work. The fact that most of my stuff is highly efficient is a side bonus.

By the way, you can do sequencing just fine. Just register multiple conditions or actions on one trigger in the order you want them run...? <_<

By all means, use whatever you like. I wrote this because I needed it, so I figured it would be useful for others. :)
 

Kenny

Back for now.
Awesome, finally a good all-purpose full damage system for maps. Looks very cool, and interface is simple as, love it.

Once again, if I actually needed damage detection, I think I found the system to use.

Nice work. :D
 

D.V.D

Make a wish
JASS:
function UnitDamageTargetEx takes lots of things returns boolean


What if a GUI user wanted to use this? He would have to search your code to find out what function calls he needs to do and even if he found it, he still might not know how to call the function.
 

Romek

Super Moderator
Staff member
This system wasn't designed for GUI users.
 

D.V.D

Make a wish
But what if a GUI user want to use it? You could atleast just wright what the function takes.
 

uberfoop

~=Admiral Stukov=~
'But what if a foreign medical aid service wanted to use the Topol-M missile launch vehicle? You should at least include instructions on how to load medical supplies into the missile tube.'


Using AIDS within GUI would be horrifying to consider even if you were a master of both Jass and GUI. There's really no point facilitating it. I mean, if you're so beastly that you can actually use AIDS within GUI's structure, you'll have no trouble figuring out what that function takes.
 

Darthfett

Super Mod
But what if a GUI user want to use it?
Then he can spend the day or two learning JASS, instead of the week or two trying to figure out how to use a custom event system within GUI.

There are very few systems that should actually be used within GUI, and those are the one-function-call do-it-all-for-you systems, such as a knockback system.

Since there is no native way to add GUI function calls, every part of a system like this would need to deal with custom script. There are even conditional functions in here. There isn't any *native* custom script for conditions.

I would recommend adding in documentation about what the returned booleans mean for the functions, but other than that, good job. :)
 
General chit-chat
Help Users
  • No one is chatting at the moment.
  • The Helper The Helper:
    I read in the PC Gamer review that it was not much of an update to the game like just a re-release
  • Ghan Ghan:
    It's a good remaster. They did not want to change the game much because there are still many die-hard players out there that wouldn't like it to be a new game.
  • Ghan Ghan:
    They've said they are open to adding new content in the future, but wanted to get what's there correct first. As it is, for what you get the $40 asking price is a bit steep.
  • The Helper The Helper:
    Yeah that is a high price think I am going to wait a minute and see if it goes down some.
  • The Helper The Helper:
    Looking forward to doing some dungeon grinding again though.
  • The Helper The Helper:
    still 101 bots on the site constantly
  • midnight8 midnight8:
    my bots got out of their cage? oh no
  • The Helper The Helper:
    these bots are from russia, china and asia they are less than they were but more than they need to be I have no idea why they hover here
  • tom_mai78101 tom_mai78101:
    Probably because of my Headline News
  • The Helper The Helper:
    Facebook is getting fucked right now lol
  • The Helper The Helper:
    I learned one thing from the Facebook outage - BGP - I am sure Ghan is an expert in it but I did not know what the name of it was I knew there was something like that there though
  • The Helper The Helper:
    What is up Tom?
  • jonas jonas:
    We once considered doing a project on BGP looking for problems in the implementations. But we didn't get sufficient interest and Tony Li was mean to us so we said whatever
  • Varine Varine:
    hello, peeps. I got most of my computer back up to Idaho so I have passwords again after that nightmare of like 4 months
  • Varine Varine:
    We're all trying to get time off now, but holy shit. I did not expect it to be that busy. Every day was a historical record, and every week was a total sales record for like all of summer. My pay is going to almost double next summer, which makes cooking finally a viable career, I might actually afford to change industries soon!
  • Varine Varine:
    We had people ask if there was anything we could do about the fucking sun. There's like 30 inside tables and every outside table has a sunbrella thing.
  • Varine Varine:
    No, I'm sorry, I cannot move the sun and the umbrella doesn't go 90 degrees sideways. They unfortunately had to deal with a sunset across mountains and a lake, we can only imagine the horror they had to undergo by visiting our restaurant
  • Varine Varine:
    Well, they do, but they're big and blow away and a 10 foot runaway umbrella is a much bigger problem than some cunt with the sun sorta in her eyes
  • The Helper The Helper:
    What is up Varine? Good to see you buddy! Too bad you are not in Houston I have a friend that needs Cooking staff like desperately remember if you ever want to get a different landscape, not sunsets over mountains and a lake Houston has opportunity!
  • tom_mai78101 tom_mai78101:
    Nice to see Varine back in action.
  • The Helper The Helper:
    Welcome back Varine!
  • The Helper The Helper:
    can someone answer a question in the World Editor Help forum?
  • jonas jonas:
    ok
  • The Helper The Helper:
    thank you jonas!
  • Varine Varine:
    I'll probably be here through next summer at least, I get paid decent at the moment and I expect it to go up significantly next year. Plus I'm on contract through the end of the year
    +1

    Members online

    No members online now.

    Affiliates

    Hive Workshop NUON Dome
    Top