• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[vJASS] Ghostly

Level 13
Joined
Nov 7, 2014
Messages
571
Ghostly is a library that enables the player camera to "free float" inside the map.
Because you can't detect mouse movement in [ordinary] War3, the keys that can be detect
are used, i.e: the arrow keys (left, right, up, down) and the Escape (Esc) key.

The Esc key toggles between 2 modes. The 1st mode is "looking-around" and
the 2nd is "moving-around". There's an automove flag that propels the player's
camera forward so that he would only have to "look around" and not have to switch
between the two modes.

How is it usefull? Well... I think it's quite fun and could be used
by terrain makers to have an in-game look (i.e not just looking inside the world-editor)
of their well... terrain from different angles.


So in order to use Ghostly you have to import it into your map:
JASS:
library Ghostly uses OnKeyEvent, UnitZ

static if false then
    ## Ghostly

    #### How to use

    First get a Ghost from a player:

        player p = Player(0)
        Ghost  g = Ghost(GetPlayerId(p))

    Then get/set the Ghost properties:

        g.p: Is the player who''s Ghost g is.

        g.dummy: Is the underlying unit that is used to anchor the camera.

                 This is required because by default the arrow keys move the
                 camera unless SetCameraTargetController is called (which
                 takes a unit).

                 NOTE: Make sure to copy the dummy unit (DUMMY_ID = 'e000')
                       from the map.
                       It is actually the one from the Caster System so it is
                       likely that you already have it.


        g.cx, g.cy, g.cz: specify the position of the Ghost on the map

        g.camera_speed: how fast the Ghost moves in units per second


        g.rot: short for rotation, aka yaw angle
        g.rot_speed: how fast the Ghost is rotating (left-right) in units per second

        g.aoa: short for angle of attack, aka pitch angle
        g.aoa_speed: how fast the Ghost is rotating (up-down) in units per second


        g.farz: how far the war3 engine "sees"/renders from g.cx, g.cy, g.cz

        g.target_distance: the distance from the actual position of the camera
                           to the anchored g.dummy

        g.field_of_view: the field of view of the camera


        g.is_moving: when set the Ghost is in "move-around" mode

        g.enabled: if not set the player is using the "default" camera

        g.is_automoving: when set the player is propelled forward automatically

endif

globals
    private constant real DT = 1.0 / 64

    private constant integer DUMMY_ID = 'e000'

    private constant boolean DEFAULT_IS_MOVING = false // set to false for rotating

    private constant boolean ENABLE_ON_STARTUP = true  // else Ghostly(GetPlayerId(p)).enabled = true

    private constant boolean DEFAULT_IS_AUTOMOVING = false

    private constant boolean SET_CAMERA_BOUNDS_TO_WORLD_BOUNDS = false

    private constant boolean CAN_MOVE_THROUGH_TERRAIN = false

    // starting position of the camera
    private constant real DEFAULT_CX = 0
    private constant real DEFAULT_CY = 0
    private constant real DEFAULT_CZ = 500

    private constant real DEFAULT_CAMERA_SPEED = 1000

    private constant real DEFAULT_ROT = 90
    private constant real DEFAULT_ROT_SPEED = 60.0
    private constant real DEFAULT_AOA = 304
    private constant real DEFAULT_AOA_SPEED = 60.0

    private constant real DEFAULT_FARZ = 5000
    private constant real DEFAULT_TARGET_DISTANCE = 0
    private constant real DEFAULT_FIELD_OF_VIEW = 120

    // The camera get's stuck at this height...
    private constant real CAMERA_MAX_Z = 4332 // ~4332
endglobals


// Without this function this library would suck!!
// [url]http://www.wc3c.net/showthread.php?p=1029058[/url]
// <3 Tc =)
private function SetCameraZ takes real z returns nothing
    set z = GetCameraField(CAMERA_FIELD_ZOFFSET) + z - GetCameraTargetPositionZ()
    call SetCameraField(CAMERA_FIELD_ZOFFSET, z, -0.01)
    call SetCameraField(CAMERA_FIELD_ZOFFSET, z, 0.01)
endfunction


private function cos takes real deg returns real
    return Cos(deg * bj_DEGTORAD)
endfunction
private function sin takes real deg returns real
    return Sin(deg * bj_DEGTORAD)
endfunction


globals
    private timer ticker = CreateTimer()

    private real bound_left
    private real bound_right
    private real bound_top
    private real bound_bottom

    // private real margin_left
    // private real margin_right
    // private real margin_top
    // private real margin_bottom

    private real camera_left
    private real camera_right
    private real camera_top
    private real camera_bottom
    private real camera_up
endglobals

struct Ghost extends array
    player p
    unit dummy

    real cx
    real cy
    real cz
    real camera_speed

    real rot
    real rot_speed

    real aoa
    real aoa_speed

    real farz
    real target_distance
    real field_of_view

    boolean is_moving
    boolean enabled
    boolean is_automoving

    private static method create takes Ghost this, player p returns Ghost
        set this.p = p

        set dummy = CreateUnit(p, DUMMY_ID, 0, 0, 0)
        // set dummy = CreateUnit(p, 'hfoo', 0, 0, 0)
        call UnitAddAbility(dummy, 'Amrf')
        call UnitRemoveAbility(dummy, 'Amrf')
        call UnitAddAbility(dummy, 'Aloc')
        call SetUnitPathing(dummy, false)

        set cx = DEFAULT_CX
        set cy = DEFAULT_CY
        set cz = DEFAULT_CZ

        set camera_speed = DEFAULT_CAMERA_SPEED

        set rot       = DEFAULT_ROT
        set rot_speed = DEFAULT_ROT_SPEED
        set aoa       = DEFAULT_AOA
        set aoa_speed = DEFAULT_AOA_SPEED

        set farz            = DEFAULT_FARZ
        set target_distance = DEFAULT_TARGET_DISTANCE
        set field_of_view   = DEFAULT_FIELD_OF_VIEW

        set is_moving = DEFAULT_IS_MOVING

        set enabled = ENABLE_ON_STARTUP

        set is_automoving = DEFAULT_IS_AUTOMOVING

        return this
    endmethod

    method update_camera takes nothing returns nothing
        // bounds checks x, y
        if cx < camera_left then
            set cx = camera_left
        elseif cx > camera_right then
            set cx = camera_right
        endif
        if cy > camera_top then
            set cy = camera_top
        elseif cy < camera_bottom then
            set cy = camera_bottom
        endif
        call SetUnitX(dummy, cx)
        call SetUnitY(dummy, cy)
        // call SetUnitPosition(dummy, cx, cy)
        // call SetUnitFacing(dummy, rot)

        // bounds check z
static if CAN_MOVE_THROUGH_TERRAIN then
        if cz > camera_up then
            set cz = camera_up
        endif
else
        if cz < GetTerrainZ(cx, cy) + 128 then
            set cz = GetTerrainZ(cx, cy) + 128
        elseif cz > camera_up then
            set cz = camera_up
        endif
endif
        call SetUnitZ(dummy, cz)

        if is_moving and is_automoving then
            // Follow that direction vector!
            set cx = cx + cos(rot) * cos(aoa) * camera_speed * DT
            set cy = cy + sin(rot) * cos(aoa) * camera_speed * DT
            set cz = cz + sin(aoa) * camera_speed * DT
        endif

        if GetLocalPlayer() == p then
            // prevent Home/End and PageUp/Down fiddling with the camera
            call SetCameraField(CAMERA_FIELD_FARZ, farz, 0)
            call SetCameraField(CAMERA_FIELD_TARGET_DISTANCE, target_distance, 0)
            call SetCameraField(CAMERA_FIELD_FIELD_OF_VIEW, field_of_view, 0)
            call SetCameraField(CAMERA_FIELD_ROTATION, rot, 0)
            call SetCameraField(CAMERA_FIELD_ANGLE_OF_ATTACK, aoa, 0)

            // The camera seems to shake more if we don't use
            // SetCameraTargetController(...)
            // call SetCameraPosition(cx, cy)

            // so we use it, instead
            call SetCameraTargetController(dummy, 0, 0, false)

            call SetCameraZ(cz)
        endif

    endmethod

    method left takes nothing returns nothing
        if is_moving and not is_automoving then
            set cx = cx + cos(rot + 90) * camera_speed * DT
            set cy = cy + sin(rot + 90) * camera_speed * DT
        else
            set rot = rot + rot_speed * DT
            if GetLocalPlayer() == p then
                call SetCameraField(CAMERA_FIELD_ROTATION, rot, 0)
            endif
        endif
    endmethod

    method right takes nothing returns nothing
        if is_moving and not is_automoving then
            set cx = cx + cos(rot - 90) * camera_speed * DT
            set cy = cy + sin(rot - 90) * camera_speed * DT
        else
            set rot = rot - rot_speed * DT
            if GetLocalPlayer() == p then
                call SetCameraField(CAMERA_FIELD_ROTATION, rot, 0)
            endif
        endif
    endmethod

    method up takes nothing returns nothing
        if is_moving and not is_automoving then
            set cx = cx + cos(rot) * cos(aoa) * camera_speed * DT
            set cy = cy + sin(rot) * cos(aoa) * camera_speed * DT
            set cz = cz + sin(aoa) * camera_speed * DT
        else
            set aoa = aoa + aoa_speed * DT
            if GetLocalPlayer() == p then
                call SetCameraField(CAMERA_FIELD_ANGLE_OF_ATTACK, aoa, 0)
                // call AdjustCameraField(CAMERA_FIELD_ANGLE_OF_ATTACK, aoa_speed * DT, 0.01)
            endif
        endif
    endmethod

    method down takes nothing returns nothing
        if is_moving and not is_automoving then
            set cx = cx - cos(rot) * cos(aoa) * camera_speed * DT
            set cy = cy - sin(rot) * cos(aoa) * camera_speed * DT
            set cz = cz - sin(aoa) * camera_speed * DT
        else
            set aoa = aoa - aoa_speed * DT
            if GetLocalPlayer() == p then
                call SetCameraField(CAMERA_FIELD_ANGLE_OF_ATTACK, aoa, 0)
                // call AdjustCameraField(CAMERA_FIELD_ANGLE_OF_ATTACK, -aoa_speed * DT, 0.01)
            endif
        endif
    endmethod

    private static method on_key takes player p, integer key, boolean is_down returns nothing
        local Ghost g = Ghost(GetPlayerId(p))

        if not g.enabled then
            return
        endif

        if     key == KEY_LEFT and is_down then
            call g.left()

        // elseif key == KEY_LEFT then // and not is_down

        elseif key == KEY_RIGHT and is_down then
            call g.right()

        // elseif key == KEY_RIGHT then // and not is_down

        elseif key == KEY_UP and is_down then
            call g.up()

        // elseif key == KEY_UP  then // and not is_down

        elseif key == KEY_DOWN and is_down then
            call g.down()

        // elseif key == KEY_DOWN then // and not is_down

        elseif key == KEY_ESC then
            set g.is_moving = not g.is_moving
            call ClearSelectionForPlayer(g.p)
        endif
    endmethod
    implement onkeyevent

    private static method tick takes nothing returns nothing
        local integer i
        local player  p
        local integer pid
        local Ghost   g

        set i = 0
        loop
            exitwhen i >= bj_MAX_PLAYERS
            set p = Player(i)
            set pid = i
            set g = Ghost(pid)

            if g.enabled then
                if is_player_key_down[pid][KEY_LEFT] then
                    call g.left()
                endif
                if is_player_key_down[pid][KEY_RIGHT] then
                    call g.right()
                endif
                if is_player_key_down[pid][KEY_UP] then
                    call g.up()
                endif
                if is_player_key_down[pid][KEY_DOWN] then
                    call g.down()
                endif

                call g.update_camera()
            endif

            set i = i + 1
        endloop
    endmethod

    private static method onInit takes nothing returns nothing
        local integer i

        static if SET_CAMERA_BOUNDS_TO_WORLD_BOUNDS then
            call SetCameraBoundsToRect(GetWorldBounds())
        endif

        set bound_left   = GetCameraBoundMinX()
        set bound_right  = GetCameraBoundMaxX()
        set bound_top    = GetCameraBoundMaxY()
        set bound_bottom = GetCameraBoundMinY()
        // call BJDebugMsg(R2S(bound_left))
        // call BJDebugMsg(R2S(bound_right))
        // call BJDebugMsg(R2S(bound_top))
        // call BJDebugMsg(R2S(bound_bottom))


        // set margin_left   = GetCameraMargin(CAMERA_MARGIN_LEFT)
        // set margin_right  = GetCameraMargin(CAMERA_MARGIN_RIGHT)
        // set margin_top    = GetCameraMargin(CAMERA_MARGIN_TOP)
        // set margin_bottom = GetCameraMargin(CAMERA_MARGIN_BOTTOM)
        // // call BJDebugMsg(R2S(margin_left))
        // // call BJDebugMsg(R2S(margin_right))
        // // call BJDebugMsg(R2S(margin_top))
        // // call BJDebugMsg(R2S(margin_bottom))

        set camera_left   = bound_left   //- margin_left
        set camera_right  = bound_right  //+ margin_right
        set camera_top    = bound_top    //+ margin_top
        set camera_bottom = bound_bottom //- margin_bottom
        set camera_up     = CAMERA_MAX_Z

        // initialize all ghosts
        set i = 0
        loop
            exitwhen i >= bj_MAX_PLAYERS
            // creating ghosts out of thin air :P
            call Ghost.create(Ghost(i), Player(i))
            set i = i + 1
        endloop

        call TimerStart(ticker, DT, true, function thistype.tick)

        // Is this global/for all players or player specific?
        // Is there a difference between 1 and 100?
        // If there is, I can't notice it, lets
        // be on the smoother side anyway.
        call CameraSetSmoothingFactor(100)
        // The default seems to be 0?
    endmethod
endstruct

endlibrary

but becuase it uses 2 more libraries you have to import them too... oh well:

OnKeyEvent:

JASS:
library OnKeyEvent

// An example of how to use the onkeyevent module:
//
// struct MyStruct extends array
//
//     private static method on_key takes player p, integer key, boolean is_down returns nothing
//         call BJDebugMsg(GetPlayerName(p))
//
//         if     key == KEY_LEFT and is_down then
//             call BJDebugMsg("key left down")
//
//         elseif key == KEY_LEFT then // and not is_down
//             call BJDebugMsg("key left up")
//
//
//         elseif key == KEY_RIGHT and is_down then
//             call BJDebugMsg("key right down")
//
//         elseif key == KEY_RIGHT then // and not is_down
//             call BJDebugMsg("key right up")
//
//
//         elseif key == KEY_UP and is_down then
//             call BJDebugMsg("key up down")
//
//         elseif key == KEY_UP  then // and not is_down
//             call BJDebugMsg("key up up")
//
//
//         elseif key == KEY_DOWN and is_down then
//             call BJDebugMsg("key down down")
//
//         elseif key == KEY_DOWN then // and not is_down
//             call BJDebugMsg("key down up")
//
//
//         elseif key == KEY_ESC then
//             call BJDebugMsg("esc key pressed")
//
//         endif
//
//     endmethod
//     implement onkeyevent
//
// endstruct

globals
    /* export */ constant integer KEY_LEFT  = 0
    /* export */ constant integer KEY_RIGHT = 1
    /* export */ constant integer KEY_DOWN  = 2
    /* export */ constant integer KEY_UP    = 3

    private constant integer MAX_PLAYERS = 12 // bj_MAX_PLAYERS
    // meant to be used inside a timer handler function / loop
    /* export */ boolean array is_player_key_down[MAX_PLAYERS][4]

    constant integer KEY_ESC   = 4
endglobals

private module OnKeyEvent
    static trigger arrow_key_trigger = CreateTrigger()
    static trigger esc_key_trigger   = CreateTrigger()
    static player  p
    static integer key
    static boolean is_down


    private static method on_arrow_key takes nothing returns boolean
        // EVENT_PLAYER_ARROW_LEFT_DOWN  = ConvertPlayerEvent(261) // 0
        // EVENT_PLAYER_ARROW_LEFT_UP    = ConvertPlayerEvent(262) // 1
        // EVENT_PLAYER_ARROW_RIGHT_DOWN = ConvertPlayerEvent(263) // 2
        // EVENT_PLAYER_ARROW_RIGHT_UP   = ConvertPlayerEvent(264) // 3
        // EVENT_PLAYER_ARROW_DOWN_DOWN  = ConvertPlayerEvent(265) // 4
        // EVENT_PLAYER_ARROW_DOWN_UP    = ConvertPlayerEvent(266) // 5
        // EVENT_PLAYER_ARROW_UP_DOWN    = ConvertPlayerEvent(267) // 6
        // EVENT_PLAYER_ARROW_UP_UP      = ConvertPlayerEvent(268) // 7
        local integer eid = GetHandleId(GetTriggerEventId()) - 261

        set p       = GetTriggerPlayer()
        set key     = eid / 2
        set is_down = eid == 2 * key
        set is_player_key_down[GetPlayerId(p)][key] = is_down

        call TriggerEvaluate(arrow_key_trigger)

        return false
    endmethod

    private static method on_esc_key takes nothing returns boolean
        set p       = GetTriggerPlayer()
        set key     = KEY_ESC
        set is_down = true

        call TriggerEvaluate(esc_key_trigger)

        return false
    endmethod

    private static method onInit takes nothing returns nothing
        local player p
        local trigger t
        local integer i

        set t = CreateTrigger()
        set i = 0
        loop
            exitwhen i >= bj_MAX_PLAYERS

            set p = Player(i)
            if GetPlayerSlotState(p) == PLAYER_SLOT_STATE_PLAYING and GetPlayerController(p) == MAP_CONTROL_USER then

                call TriggerRegisterPlayerEvent(t, p, EVENT_PLAYER_ARROW_LEFT_DOWN)
                call TriggerRegisterPlayerEvent(t, p, EVENT_PLAYER_ARROW_LEFT_UP)
                call TriggerRegisterPlayerEvent(t, p, EVENT_PLAYER_ARROW_RIGHT_DOWN)
                call TriggerRegisterPlayerEvent(t, p, EVENT_PLAYER_ARROW_RIGHT_UP)
                call TriggerRegisterPlayerEvent(t, p, EVENT_PLAYER_ARROW_DOWN_DOWN)
                call TriggerRegisterPlayerEvent(t, p, EVENT_PLAYER_ARROW_DOWN_UP)
                call TriggerRegisterPlayerEvent(t, p, EVENT_PLAYER_ARROW_UP_DOWN)
                call TriggerRegisterPlayerEvent(t, p, EVENT_PLAYER_ARROW_UP_UP)

            endif

            set i = i + 1
        endloop
        call TriggerAddCondition(t, Condition(function thistype.on_arrow_key))

        set t = CreateTrigger()
        set i = 0
        loop
            exitwhen i >= bj_MAX_PLAYERS

            set p = Player(i)
            if GetPlayerSlotState(p) == PLAYER_SLOT_STATE_PLAYING and GetPlayerController(p) == MAP_CONTROL_USER then
                call TriggerRegisterPlayerEvent(t, p, EVENT_PLAYER_END_CINEMATIC)
            endif

            set i = i + 1
        endloop
        call TriggerAddCondition(t, Condition(function thistype.on_esc_key))

        set t = null
    endmethod
endmodule
private struct OnKeyEventDummy extends array
    implement OnKeyEvent
endstruct


module onkeyevent
    private static method call_on_key_event_handler takes nothing returns boolean
        call thistype.on_key(OnKeyEventDummy.p, OnKeyEventDummy.key, OnKeyEventDummy.is_down)
        return false
    endmethod

    private static method onInit takes nothing returns nothing
        static if thistype.on_key.exists then
            call TriggerAddCondition(OnKeyEventDummy.arrow_key_trigger, Condition(function thistype.call_on_key_event_handler))
            call TriggerAddCondition(OnKeyEventDummy.esc_key_trigger, Condition(function thistype.call_on_key_event_handler))
        endif
    endmethod
endmodule


endlibrary

JASS:
UnitZ:
[code=jass]
library UnitZ

globals
    private location loc = Location(0., 0.)
endglobals

function GetTerrainZ takes real x, real y returns real
    call MoveLocation(loc, x, y)
    return GetLocationZ(loc)
endfunction

// function GetUnitZ takes unit u returns real
//     return GetTerrainZ(GetUnitX(u), GetUnitY(u)) + GetUnitFlyHeight(u)
// endfunction

// function SetUnitZ takes unit u, real z returns nothing
//     call SetUnitFlyHeight(u, z - GetTerrainZ(GetUnitX(u), GetUnitY(u)), 0.0)
// endfunction


// inline the GetTerrainZ call

function GetUnitZ takes unit u returns real
    call MoveLocation(loc, GetUnitX(u), GetUnitY(u))
    return GetLocationZ(loc) + GetUnitFlyHeight(u)
endfunction

function SetUnitZ takes unit u, real z returns nothing
    call MoveLocation(loc, GetUnitX(u), GetUnitY(u))
    call SetUnitFlyHeight(u, z - GetLocationZ(loc), 0.0)
endfunction

endlibrary

edit:
Added a much improved version (ghostly-mem-hack-demo.w3x) that uses mem/hack. Currently does not work in the latest released patch (1.27b).

PS: All your camera angles are belong to us!
 

Attachments

  • ghostly-demo.w3x
    50 KB · Views: 114
  • ghostly-mem-hack-demo.w3x
    331.1 KB · Views: 96
Last edited:
Level 14
Joined
Dec 12, 2012
Messages
1,007
I like it, but there are some things to consider/discuss.

I think it would be better (in a sense that it would be more generic and therefore applicable to more situations) if you decouple the camera control from the keyboard. Maybe someone wants to create a spell that makes the camera to perform a backflip - then he doesn't want to use keyevents at all.

Therefore it would be better to have just an API for camera control and let the keyboard library be an example implementation.

Also your member variables should use visibility modifiers. At the moment a user can just set g.is_moving = false although the camera keeps moving.

Also the constructor should take a player rather than an integer and a player, which is redundant and error prone (what if I pass Player(1) but an integer of 2?). You register a Ghost for a player, so construct it with a player. Then you also don't have to check whether the integer is in an allowed player range (which you aren't doing at the moment, so its possible to pass invalid integers that don't correspond to a specific player).

Finally some small thoughts:

  • There is quite some code that is commented out. Such dead/old code comments should be removed in a proposal.
  • In your globals section you have a mixture of configurable globals (e.g. ENABLE_ON_STARTUP or DUMMY_ID) and non-configurable globals (CAMERA_MAX_Z, DEFAULT_ROT, etc.) since those are default values. Those should be seperated clearly such that a user can see what he is supposed to configure and what not.
  • If I have a complete backflip with the camera, the screen starts to oscillate, maybe you can fix that?
  • About the static if false then, its a creative method, but I think a standard comment would be less confusing to users.
 
Level 13
Joined
Nov 7, 2014
Messages
571
I think it would be better (in a sense that it would be more generic and therefore applicable to more situations) if you decouple the camera control from the keyboard. Maybe someone wants to create a spell that makes the camera to perform a backflip - then he doesn't want to use keyevents at all.

Therefore it would be better to have just an API for camera control and let the keyboard library be an example implementation.

Aren't all the Camera* natives and the camera functions from blizzard.j an
"API for camera control" already? Ghostly uses those so if I "decouple the camera control from the keyboard"
we will end up with them.

Also your member variables should use visibility modifiers. At the moment a user can just set g.is_moving = false although the camera keeps moving.

Struct members are public by default, no need for a modifier.

If g.is_moving is set to false, it means the camera is in "rotate-around" mode.

If g.is_moving is set to false the camera shouldn't be moving because all the
code that moves the camera (i.e modifing the cx, cy, cz variables) is inside if g.is_moving ... blocks.

Also the constructor should take a player rather than an integer and a player , which is redundant and error prone (what if I pass Player(1) but an integer of 2?). You register a Ghost for a player, so construct it with a player. Then you also don't have to check whether the integer is in an allowed player range (which you aren't doing at the moment, so its possible to pass invalid integers that don't correspond to a specific player).

It's supposed to be private, each player can only have 1 Ghost that can be referenced with: Ghost g = Ghost(GetPlayerId(p)).

In your globals section you have a mixture of configurable globals (e.g. ENABLE_ON_STARTUP or DUMMY_ID ) and non-configurable globals ( CAMERA_MAX_Z , DEFAULT_ROT , etc.) since those are default values. Those should be seperated clearly such that a user can see what he is supposed to configure and what not.

Those are all configurable.

If I have a complete backflip with the camera, the screen starts to oscillate, maybe you can fix that?

It should be fixed, you probably have the old map?

I am not sure what was going on, but even when the system was disabled for
the player and the camera was reseted to the default game camera it was still
"rotating" slowly for some reason...

JASS:
    // prevent Home/End and PageUp/Down fiddling with the camera
    call SetCameraField(CAMERA_FIELD_FARZ, farz, 0)
    call SetCameraField(CAMERA_FIELD_TARGET_DISTANCE, target_distance, 0)
    call SetCameraField(CAMERA_FIELD_FIELD_OF_VIEW, field_of_view, 0)
    call SetCameraField(CAMERA_FIELD_ROTATION, rot, 0)
    call SetCameraField(CAMERA_FIELD_ANGLE_OF_ATTACK, aoa, 0)

Those calls had a 0.01 duration instead of 0, and I think that was the problem.

Maybe a call to CameraSetSmoothingFactor could help as well, I am not sure.

About the static if false then , its a creative method, but I think a standard comment would be less confusing to users.

I find it less unreadable, having it all green (depending on the syntax highlighting) is aweful.
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
Aren't all the Camera* natives and the camera functions from blizzard.j an
"API for camera control" already? Ghostly uses those so if I "decouple the camera control from the keyboard"
we will end up with them.

Not really, the camera control natives are just the basic API. Performing more complex trajectories requires angle calculations, like the ones you did in this system. The system has about 400 lines of code where only about 30 are related to the keyboard input, so I don't think we will end up with only natives.

Of course you can keep it that way, but then it fits better into the spells section. The JASS Resources section is more for general purpose systems that are rather generic, reusable and map independent. At the moment, this serves a quite specific use case, which is also perfectly fine, for a spell.


Struct members are public by default, no need for a modifier.

Sure, but say I set camera_speed to zero. Will is_moving still give correct results? Maybe its just the name thats confusing, but by a member named is_moving, I expect a getter that tells me if the camera is currently moving or not.


It's supposed to be private, each player can only have 1 Ghost that can be referenced with: Ghost g = Ghost(GetPlayerId(p)).

What if someone calls Ghost(123)? I understand what you tried here, something like a singleton * 12. But I think just checking if player x is already registered is more convenient and less confusing.

That would give the user the option to register the Ghost whenever he wants it, because at the moment you create all Ghosts and start a timer on map init.

Those are all configurable.

Then the names are a bit misleading, because by DEFAULT_*, I would expect a default value that is taken if I don't do any configurations.

I find it less unreadable, having it all green (depending on the syntax highlighting) is aweful.

Perfect, less unreadable == more readable, so go with green ;)
There are some conventions jass resources have to follow. Comments are not explicitly mentioned there, but you might want to read that anyway.
 
Top