In response to Tsenses
So I skipped the Easter weekend, and procrastinated last week :p

Well, I'm back.. and just tried to figure out what Lummox mentioned last time out.

So here's the first thing I needed to learn :

Lummox wrote:
You should not be using usr in the Angle() proc. It's bad practice to use the usr var in procs...
// version 1: global proc
proc/Angle(atom/A, atom/B)
// angle from A to B
...

// version 2: atom proc
atom/proc/AngleFrom(atom/ref)
// angle from ref to src
...
</quote>


So I understand now.. I had to modify my codes to do something like this:

proc/Angle(atom/A, atom/B)
return 90-arctan((A.y-B.y)/(A.x-B.x))


instead of what I did earlier:

atom/proc/Angle()
return 90-arctan((src.y-usr.y)/(src.x-usr.x))


.. Especially if I used this proc in another place, and the usr and src are different.. I think I get it now :D

***

On the other hand, to call this proc, I now needed to have

mob/verb/rotate()
var/icon/I = new('topdownshooter.dmi') //create a new var called "I" as an icon type.
var/n = Angle(usr,src)

**Note: This will be edited again below**

instead of what I had earlier:

mob/verb/rotate()
var/icon/I = new('topdownshooter.dmi') //create a new var called "I" as an icon type.
var/n = atom.Angle()


Right.. let's see what else I can learn before the weekend is up :p

*****

So moving on to point #3 from Lummox..

3. The angles calculated range from 0 to 90.. whereas I'm trying to get it to tell me 0 to 360..

arctan() on its own will only ever range from -90° to 90°. What you need is a specialized proc, which I've written.

Now me, I prefer to deal with angles in a nautical format, where 0=north and it goes clockwise. If you use the mathematical convention where 0=east and it goes counterclockwise, the code would differ. Here's an atan2() snippet using the latter:

proc/atan2(dx, dy)
> // special case: vertical or no difference
> if(!dx) return (dy>0) ? 90 : ((dy<0) ? -90 : 0)
> // . is the default return value
> . = arctan(dy/dx)
> if(dx < 0) . = 180 - .
> if(. < 0) . += 360

Therefore the angle from A to B would be atan2(B.x-A.x, B.y-A.y).

.. I'm so slow I took 10mins to understand how to use it first of all [so I had to change "var/n = atan2(usr,src)" instead of "var/n = Angle(usr,src)" in my rotate() verb. ]..

.. but still don't understand how you got those numbers :p (but it works obviously).. although it's giving me between 0 to 90 deg. Maybe I'll try and figure out how to get it to 360.. hmm..

*****

Well, to do point #4, I thought I might as well make this another "lesson" because I need to learn about writing if statements in DM (pretty trivial I know :p).. but I keep forgetting.. and I guess it's nice to see my own thought process in learning this
In response to Tsenses
Lesson 6: IF statements

This is my first time writing IFs in dm.. but I guess this should be one of the easier things to learn.. (I hope).. but should be baby steps for most people..

Made these changes:
proc/Angle(atom/A, atom/B)
if (A.x==B.x) return 0
if (A.x<B.x && A.y<=B.y) return 90-arctan((A.y-B.y)/(A.x-B.x)) //This looks at NE quadrant only

*Note: This will be completed below*

instead of original:

proc/Angle(atom/A, atom/B)
return 90-arctan((A.y-B.y)/(A.x-B.x))


.. So now every time I click on a tile directly above or below my character, I don't get an error message anymore ! Because it would just return a value of 0 to the console, instead of the 90-arctan bit..

Reference here


So I guess if I wanted to complete the whole thing (with 0 being North)

proc/Angle(atom/A, atom/B)
if (A.x==B.x && A.y<=B.y) return 0 //for directly north
if (A.x==B.x && A.y>=B.y) return 180 //for directly south
if (A.x<B.x && A.y<=B.y) return 90-arctan((A.y-B.y)/(A.x-B.x)) //This looks at NE quadrant only
if (A.x<B.x && A.y>=B.y) return 90+arctan((A.y-B.y)/(A.x-B.x)) //This looks at SE quadrant only
if (A.x>B.x && A.y>=B.y) return 270-arctan((A.y-B.y)/(A.x-B.x)) //This looks at SW quadrant only
if (A.x>B.x && A.y<=B.y) return 270+arctan((A.y-B.y)/(A.x-B.x)) //This looks at NW quadrant only


Each if is condensed in one line at the moment. But I can write it in separate line just as you would in C++ using {} brackets (I think).. or in VBA using 'if' .. 'end if', e.g. I could try rewriting the logic above differently like:

proc/Angle(atom/A, atom/B)
if (A.x==B.x)
if (A.y<=B.y) return 0
else return 180
else if (A.x<B.x)
if (A.y<=B.y) return 90-arctan((A.y-B.y)/(A.x-B.x)) //This looks at NE quadrant only
else return 90+arctan((A.y-B.y)/(A.x-B.x)) //This looks at SE quadrant only
else
if (A.y>=B.y) return 270-arctan((A.y-B.y)/(A.x-B.x)) //This looks at SW quadrant only
else return 270+arctan((A.y-B.y)/(A.x-B.x)) //This looks at NW quadrant only

.. I realised I'm repeating arctan((A.y-B.y)/(A.x-B.x)) a lot.. there must be a way of making this more concise right? But I guess I wouldn't bother with that.. it's not like I can win those 8KB competitions or something

.. but either way still gives me desired results anyway :)
In response to Tsenses
Passing arguments into procs

Objective: To get the Click proc to call my rotate proc = So that my character turns and faces the tile I clicked.

And I got it in a minute of exploring.. Oh yeayy.. I'm getting so good at this (as if)..


Firstly, I had to correct my earlier mistake:

mob/proc/rotate(var/n)
var/icon/I = new('topdownshooter.dmi') //create a new var called "I" as an icon type.
// var/n = Angle(usr,src)
// I.Blend(rgb(40,0,0)) //place a red tint over it (just to test)
src.icon = I //replace icon with the new icon called I above
// usr.icon = turn(usr.icon,-usr.angle) //commented out, this returns the icon back to its original orientation (drop this line if the angle is meant to be incremental)
src.icon = turn(src.icon,n) //before turning to the desired angle
src.angle = n //store the new angle into the mob's 'angle' variable


i.e.
1. changed rotate() from a verb previously to a proc
2. put in an argument in the proc, set as type var (?). by writing var/n in the brackets in the opening line
3. commented out "var/n = Angle(usr,src)" because it wasn't going to be set by this proc
4. added "Angle(usr,src)" to my Click() proc, so that it sets the value of n to be the angle (as an argument).

Meaning my click procedure now has a new line at the bottom

turf/Click()
usr << Angle(usr,src)
usr.rotate(Angle(usr,src))


Where Angle(usr,src) calls the procedure completed in lesson 6 above, returns it to the variable 'n', which then is passed through the rotate() proc. I think that's about right now?

Well in any case, my character is turning lol - although it screws up when I'm not actually moving/facing North to begin.. Well I can sort of fix it by doing this:

turf/Click()
usr << Angle(usr,src)
usr.dir = NORTH // makes my icon turn north first
usr.rotate(Angle(usr,src))

In response to Tsenses
Question: How do I do those little gif animations :P
In response to Tsenses
The /icon datum is (mostly) only needed for doing multiple operations on an icon, like a turn and flip and blend all together. Therefore in this code you don't need it, and you can eliminate the lines with I and condense the turn down to:

icon = turn('topdownshooter.dmi', n)

What you're doing currently isn't using the /icon datum at all; you're loading it, but then basically saying you didn't need the datum after all and all you wanted was the original 'topdownshooter.dmi', which you then turn in the next statement.

Probably the use of I is a relic from when you were doing the blending, but you don't need that anymore.

A couple of suggestions, BTW. Turning the icon every time is very inefficient and can fill up your cache with new icon files--especially if you don't round the angle. There are two approaches I can recommend.

Option 1: Use the transform var, and don't rotate the icon at all. This was a major addition in version 500. It should actually be better-looking visually, it's easier, and it's nicer to the clients by avoiding icon downloads. I recommend rounding the angle for display purposes, as in option #2.

n = round(n, 1)
transform = turn(matrix(), n)
// OR, use transform = matrix(n, MATRIX_ROTATE), undocumented

Option 2: Create an icon with multiple states for each direction, and cache it.

var/const/ANGLE_INCREMENT = 5
var/list/rotatingIcons
proc/RotatedIcon(icon)
if(!rotatingIcons) rotatingIcons = new // create the list
. = rotatingIcons[icon]
if(!.)
// This code assumes the icon has only one base icon state
var/icon/I = new(icon)
var/icon/J
for(var/angle=0, angle<360, angle += ANGLE_INCREMENT)
J = new(icon)
J.Turn(angle)
I.Insert(J, "[angle]") // new icon state
. = fcopy_rsc(I) // commit icon to the cache--optional but good practice
rotatingIcons[icon] = .

atom/proc/SetRotatedIcon(i, angle)
icon = RotatedIcon(i)
angle = round(angle, ANGLE_INCREMENT) % 360
icon_state = "[angle]"

The first option is the better way to go in terms of performance and visual appeal, unless you're looking for a strict pixelated-art look (in which case, our current icon rotation routines are less than ideal anyway).
There's also the convenient switch statement that can be used in place of if branches.
switch (x)
if (1)
//...
if (2 to 10)
//...
if (11 to 1.#INF)
//...
else
//...

I may be wrong on the usage of "x to y" in switches. On mobile.
In response to Mr_Goober
switch() isn't relevant to this code, though. It's for use when you have a bunch of enumerable cases to go through.
In response to Lummox JR
Option 1 works just fine.. I have no idea how I made it so bulky :p I did mention I'm a noob rite lol
Page: 1 2