Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Matching bullet start x,y with point on sprite that gets rotated

This thread is locked; no one can reply to it. rss feed Print
Matching bullet start x,y with point on sprite that gets rotated
Elvang
Member #7,828
October 2006
avatar

I am currently making a space shooter game as a learning experience and am having trouble with matching up the bullet's start point and the point of fire on a sprite that rotates. I keep drawing a blank when I try to solve this problem, though I have a method that semi-works. If someone can point me in the right direction that'd be great. Also, does anyone know why I have to use itofix() on a fixed variable for use with fixsin() and fixcos()(Lines 37 and 38)? Code is below, not the best as I'm still learning. My temporary fix is on lines 134 to 147(end).

1#include <allegro.h>
2#include <vector>
3#include "data.h"
4 
5const int x_res = 640, y_res = 480;
6float x = 320, y = 240;
7PALETTE palette;
8BITMAP *buffer;
9BITMAP *spaceship;
10DATAFILE* images = NULL;
11 
12std::vector<float> ship_fire_x;
13std::vector<float> ship_fire_y;
14std::vector<float> ship_fire_x_dir;
15std::vector<float> ship_fire_y_dir;
16 
17void init();
18void deinit();
19void ship_fire(fixed& x_speed, fixed& y_speed, float& x, float& y, fixed& angle);
20 
21volatile long speed_counter = 0;
22 
23void increment_speed_counter()
24{
25 speed_counter++;
26}
27END_OF_FUNCTION(increment_speed_counter);
28 
29int main() {
30 init();
31 
32 fixed x_dir,y_dir;
33 fixed angle = 0;
34 while (!key[KEY_ESC]) {
35 while (speed_counter > 0)
36 {
37 x_dir = fixsin(itofix(angle));
38 y_dir = fixcos(itofix(angle));
39
40 if(key[KEY_UP])
41 {
42 x += fixtof(x_dir);
43 y -= fixtof(y_dir);
44 }
45 if(key[KEY_LEFT])
46 {
47 angle--;
48 }
49 if(key[KEY_RIGHT])
50 {
51 angle++;
52 }
53 if(key[KEY_SPACE])
54 {
55 ship_fire(x_dir, y_dir, x, y, angle);
56 }
57
58 if(ship_fire_x.empty() != true)
59 {
60 for(int i = 0; i < ship_fire_x.size(); i++)
61 {
62 ship_fire_x<i> += ship_fire_x_dir<i>;
63 ship_fire_y<i> -= ship_fire_y_dir<i>;
64 if(ship_fire_x<i> > x_res || ship_fire_x<i> < 0 || ship_fire_y<i> > y_res || ship_fire_y<i> < 0)
65 {
66 ship_fire_x.erase(ship_fire_x.begin() + i);
67 ship_fire_y.erase(ship_fire_y.begin() + i);
68 ship_fire_x_dir.erase(ship_fire_x_dir.begin() + i);
69 ship_fire_y_dir.erase(ship_fire_y_dir.begin() + i);
70 }
71 }
72 }
73 speed_counter--;
74 }
75 
76 textprintf_centre_ex(buffer, font, SCREEN_W / 2 - 50, 20, makecol(255,255,255), -1, "%f", fixtof(x_dir));
77 textprintf_centre_ex(buffer, font, SCREEN_W / 2 + 50, 20, makecol(255,255,255), -1, "%f", fixtof(y_dir));
78 textprintf_centre_ex(buffer, font, SCREEN_W / 2, 40, makecol(255,255,255), -1, "%i", ship_fire_x.size());
79
80 for(int i = 0; i < ship_fire_x.size(); i++)
81 {
82 putpixel(buffer, (int)ship_fire_x<i>, (int)ship_fire_y<i>, makecol(255,255,255));
83 }
84
85 rotate_sprite(buffer, spaceship, (int)x, (int)y, itofix(angle));
86 draw_sprite(screen, buffer, 0, 0);
87 clear_bitmap(buffer);
88 }
89 
90 deinit();
91 return 0;
92}
93END_OF_MAIN()
94 
95void init() {
96 int depth, res;
97 allegro_init();
98 depth = desktop_color_depth();
99 if (depth == 0) depth = 32;
100 set_color_depth(depth);
101 res = set_gfx_mode(GFX_AUTODETECT_WINDOWED, x_res, y_res, 0, 0);
102 if (res != 0) {
103 allegro_message(allegro_error);
104 exit(-1);
105 }
106 install_timer();
107 install_keyboard();
108
109 LOCK_VARIABLE(speed_counter);
110 LOCK_FUNCTION(increment_speed_counter);
111 install_int_ex(increment_speed_counter, BPS_TO_TIMER(60));
112
113 generate_332_palette(palette);
114 buffer = create_bitmap(x_res,y_res);
115
116 images = load_datafile("data.dat");
117 if(images == NULL)
118 {
119 set_gfx_mode(GFX_TEXT,0,0,0,0);
120 allegro_message("Could not load data.dat!");
121 exit(EXIT_FAILURE);
122 }
123
124 spaceship = (BITMAP*)images[spaceship_image].dat;
125}
126 
127void deinit() {
128 clear_keybuf();
129 destroy_bitmap(buffer);
130 destroy_bitmap(spaceship);
131 unload_datafile(images);
132}
133 
134void ship_fire(fixed& x_speed, fixed& y_speed, float& x, float& y, fixed& angle)
135{
136 float offsetx = fixtof(x_speed) * 17;
137 float offsety = fixtof(y_speed) * 15;
138 float vx = fixtof(x_speed) * 2;
139 float vy = fixtof(y_speed) * 2;
140 float xfinal = vx + x + offsetx + 8;
141 float yfinal = vy + y - offsety + 15;
142
143 ship_fire_x.push_back(xfinal);
144 ship_fire_y.push_back(yfinal);
145 ship_fire_x_dir.push_back(vx);
146 ship_fire_y_dir.push_back(vy);
147}

Jakub Wasilewski
Member #3,653
June 2003
avatar

please note that I only skimmed through the code, so I might be missing something

First, take the sprite without rotation. You decide on some coordinates (x, y) that is the origin for bullets.

Now, when the ship rotates, the coordinates for this origin point should also rotate. The relevant equations are:

x' = x cos(angle) - y sin(angle)
y' = y cos(angle) + x sin(angle)

Where (x', y') are the new coordinates of the point after the rotation by "angle". Please note that these equations are for the Y axis going up, like in standard math. On the computer screen, it's usually the other way, so you'll have to switch things around a little.
They also assume that you are rotating about (0,0), which might not be the case. If you're rotating about (u,v), you just add (u,v) to your (x,y), do the rotation, and then subtract (u,v) from (x', y').

About your itofix question, I'm not sure what is it that you have a problem with. itofix converts an "int" into a "fixed". Your angle variable is already a "fixed", so you can use it with fix***() functions right away.

I strongly encourage that you just forget about fixeds, especially if you're learning. They'll just mess with your head. If you want to use them though, read the section of manual pertaining to them thoroughly.

Anyway, I'm sure none of this makes sense, so wait until someone comes and translates :P.

---------------------------
[ ChristmasHack! | My games ] :::: One CSS to style them all, One Javascript to script them, / One HTML to bring them all and in the browser bind them / In the Land of Fantasy where Standards mean something.

Elvang
Member #7,828
October 2006
avatar

Ok, after experimenting with it for awhile, I came up with this which is as close to it being accurate as I could get it. If I use (x,y) then the point of origin for the bullets goes all over the place. Also, if I don't do itofix(angle) then the value of x_trig and y_trig never change even though angle is already a fixed.

    float x_trig = 8.5* fixtof(fixcos(itofix(angle))) + 16.5* fixtof(fixsin(itofix(angle)));
    float y_trig = 16.5* fixtof(fixcos(itofix(angle))) - 8.5* fixtof(fixsin(itofix(angle))) - 16.5;

On a side note, up to what level should I take math? I stopped after Pre-Cal as I didn't see anything I would ever use in the Calculus book.

Ceagon Xylas
Member #5,495
February 2005
avatar

Radians make this sort of thing much easier. ;D

I can't really tell what your code is trying to do... Wish I had more time to read over it, but I believe it's similar to what's happening in this post.

Okay, radians are 0 to pi*2 for a full rotation. The rotate_sprite function does require a fixed however, so lets deal with that first.
rotate_sprite(bmp,sprite,ship.x,ship.y,ftofix(ship.angle*128.0/M_PI));

As for the bullet's initial position and velocity,

1struct Ship{
2 float x,y;
3 float velX,velY;
4 float angle; //stored in radians. if you want to change the angle,
5 //don't forget to do it in small amounts... Like +/- 0.005
6}ship;
7 
8struct Bullet{
9 float x,y;
10 float velX,velY;
11}bullet;
12 
13float dist=20;//distance away from the ship
14bullet.x=dist*cos(ship.angle);
15bullet.y=dist*sin(ship.angle);
16bullet.velX=speed*cos(ship.angle);
17bullet.velY=speed*sin(ship.angle);

Elvang
Member #7,828
October 2006
avatar

Power is back on, woot.

Ok, I have converted over to radians and included math.h so I don't have to deal with fixed variables. I don't see how your solution determines the point of origin on a rotating sprite though. Heres my new code(does the same as above code but easier to read). Something is wrong with the formula used to determine the point of origin of the bullets still as they appear outside the bounds of the arena. Also, shouldn't sine be used with x and cosine with y on your formulas?

1#include <allegro.h>
2#include <vector>
3#include <math.h>
4#include "data.h" // Images data file header
5#define PI 3.1415926535
6const int x_res = 640, y_res = 480; // Screen resolution
7PALETTE palette;
8BITMAP *buffer;
9BITMAP *spaceship; // Player's ship
10DATAFILE* images = NULL; // Images datafile
11 
12struct Projectile {
13 float x, vx; // X coordinate and velocity
14 float y, vy; // Y coordinate and velocity
15 float speed;
16 };
17std::vector<Projectile> bullet; // Hold's individual data on each shot
18 
19struct Spaceship {
20 float x, vx;
21 float y, vy;
22 float angle;
23 float thrust, turn_speed; // Ship maneuverability
24 } player_ship;
25 
26void init();
27void deinit();
28void player_fire();
29//////// Speed Control ////////
30volatile long speed_counter = 0;
31 
32void increment_speed_counter()
33{
34 speed_counter++;
35}
36END_OF_FUNCTION(increment_speed_counter);
37//////// Main() ////////
38int main()
39{
40 init();
41
42 while(!key[KEY_ESC])
43 {
44 while( speed_counter > 0)
45 {
46 player_ship.vx = sin(player_ship.angle) * player_ship.thrust;
47 player_ship.vy = cos(player_ship.angle) * player_ship.thrust;
48
49 if(key[KEY_UP])
50 {
51 player_ship.x += player_ship.vx;
52 player_ship.y -= player_ship.vy;
53 }
54
55 if(key[KEY_LEFT])
56 player_ship.angle -= player_ship.turn_speed;
57
58 if(key[KEY_RIGHT])
59 player_ship.angle += player_ship.turn_speed;
60
61 if(key[KEY_SPACE])
62 player_fire();
63
64 if(bullet.empty() != true)
65 {
66 for(int i = 0; i < bullet.size(); i++)
67 {
68 bullet<i>.x += bullet<i>.vx;
69 bullet<i>.y -= bullet<i>.vy;
70
71 if(bullet<i>.x > x_res || bullet<i>.x < 0 || bullet<i>.y > y_res || bullet<i>.y < 0)
72 bullet.erase(bullet.begin() + i);
73 }
74 }
75
76 speed_counter--;
77 }
78
79 for(int i = 0; i < bullet.size(); i++)
80 putpixel(buffer, (int)bullet<i>.x, (int)bullet<i>.y, makecol(255,255,255));
81
82 rotate_sprite(buffer, spaceship, (int)player_ship.x, (int)player_ship.y, ftofix(player_ship.angle*128.0/PI));
83 draw_sprite(screen, buffer, 0, 0);
84 clear_bitmap(buffer);
85 }
86
87 deinit();
88 return 0;
89} END_OF_MAIN()
90//////// Initialization ////////
91void init() {
92 int depth, res;
93 allegro_init();
94 depth = desktop_color_depth();
95 if (depth == 0) depth = 32;
96 set_color_depth(depth);
97 res = set_gfx_mode(GFX_AUTODETECT_WINDOWED, x_res, y_res, 0, 0);
98 if (res != 0) {
99 allegro_message(allegro_error);
100 exit(-1);
101 }
102 install_timer();
103 install_keyboard();
104
105 LOCK_VARIABLE(speed_counter);
106 LOCK_FUNCTION(increment_speed_counter);
107 install_int_ex(increment_speed_counter, BPS_TO_TIMER(60));
108
109 generate_332_palette(palette);
110 buffer = create_bitmap(x_res,y_res);
111 images = load_datafile("data.dat");
112 if(images == NULL)
113 {
114 set_gfx_mode(GFX_TEXT,0,0,0,0);
115 allegro_message("Could not load data.dat!");
116 exit(EXIT_FAILURE);
117 }
118 spaceship = (BITMAP*)images[spaceship_image].dat;
119
120 player_ship.angle = 0;
121 player_ship.x = x_res / 2;
122 player_ship.y = y_res / 2;
123 player_ship.turn_speed = 0.03;
124 player_ship.thrust = 2;
125}
126//////// Deinitialization ////////
127void deinit()
128{
129 clear_keybuf();
130 destroy_bitmap(buffer);
131 destroy_bitmap(spaceship);
132 unload_datafile(images);
133}
134//////// Projectile Firing ////////
135void player_fire()
136{
137 Projectile temp_bullet;
138 temp_bullet.speed = 2;
139 int spawn_distance = 17;
140 temp_bullet.vx = temp_bullet.speed * player_ship.vx;
141 temp_bullet.vy = temp_bullet.speed * player_ship.vy;
142 temp_bullet.x = spawn_distance * cos(player_ship.angle);
143 temp_bullet.y = spawn_distance * sin(player_ship.angle);
144
145 bullet.push_back(temp_bullet);
146}

EDIT: Is the problem that my sprite is 17x31?

Ceagon Xylas
Member #5,495
February 2005
avatar

Quote:

Also, shouldn't sine be used with x and cosine with y on your formulas?

Nope: vec2b.gif

Quote:

Is the problem that my sprite is 17x31?

Nope, you're simply going to change spawn_distance to the maximum width or height of your image. So spawn_distance=31; (May want to go with 32 to make it a little safer when finding it he bullet has hit anything)

Sorry, I wasn't quite thorough in explaining the starting x and y of the bullet. Simply start at the center of your ship, move it to the outside of the ship, then set it's velocities. Something like:

temp_bullet.x=player_ship.x+spaceship->w/2+spawn_distance*cos(player_ship.angle);
temp_bullet.y=player_ship.y+spaceship->h/2+spawn_distance*sin(player_ship.angle);
//...then the velocities the same as before

Side notes:
Your definition of PI is unnecessary. Simply call M_PI

Using iterators with vectors will considerably speed up your program. I'd change this block

if(bullet.empty() != true)
  {
    for(int i = 0; i < bullet.size(); i++)
    {
      bullet<i>.x += bullet<i>.vx;
      bullet<i>.y -= bullet<i>.vy;

      if(bullet<i>.x > x_res || bullet<i>.x < 0 || bullet<i>.y > y_res || bullet<i>.y < 0)
        bullet.erase(bullet.begin() + i);
    }
}

To

for(vector<Projectile>::iterator i=bullet.begin(); i!=bullet.end(); i++) {
  i->x+=i->vx;
  i->y-=i->vy;
  if(i->x > x_res || i->x < 0 || i->y > y_res || i->y < 0) {
    //This simply swaps the bullet that needs to be deleted
    //with the last element in the vector and deletes it.
    //Much faster than resizing the entire vector.
    if(i<bullet.size()-1)
      memcpy(&bullet<i>,&bullet.back(),sizeof(bullets));
    bullet.pop_back();
  }
}

gnolam
Member #2,030
March 2002
avatar

Ceagon: You're still not rotating an arbitrary point, which (I assume) is what the OP asked for.

Elvang: just follow Jakub's post, and make sure to ditch all that silly fixed point math and instead use floats/doubles and radians.

--
Move to the Democratic People's Republic of Vivendi Universal (formerly known as Sweden) - officially democracy- and privacy-free since 2008-06-18!

Elvang
Member #7,828
October 2006
avatar

Ok, using Ceagon's formulas(with some modification, for some reason the point was 64 radians(90 degrees) over what it should have been...) it works. When I first looked into how to go about getting rid of the bullets that went out of range I was going to use an iterator, but I have no experience with them. As such, I cannot decipher what is wrong with these two lines, even after extensive searching and reading ???.

if(i < bullet.size() - 1)
     memcpy( &bullet<i>, &bullet.back(), sizeof(bullet));

Heres all of my current code.

1#include <allegro.h>
2#include <vector>
3#include <math.h>
4#include "data.h" // Images data file header
5 
6const int x_res = 640, y_res = 480; // Screen resolution
7PALETTE palette;
8BITMAP *buffer;
9BITMAP *spaceship; // Player's ship
10DATAFILE* images = NULL; // Images datafile
11 
12struct Projectile {
13 float x, vx; // X coordinate and velocity
14 float y, vy; // Y coordinate and velocity
15 float speed;
16 };
17std::vector<Projectile> bullet; // Hold's individual data on each shot
18 
19struct Spaceship {
20 float x, vx;
21 float y, vy;
22 float angle;
23 float thrust, turn_speed; // Ship maneuverability
24 } player_ship;
25 
26void init();
27void deinit();
28void player_fire();
29//////// Speed Control ////////
30volatile long speed_counter = 0;
31 
32void increment_speed_counter()
33{
34 speed_counter++;
35}
36END_OF_FUNCTION(increment_speed_counter);
37//////// Main() ////////
38int main()
39{
40 init();
41
42 while(!key[KEY_ESC])
43 {
44 while( speed_counter > 0)
45 {
46 player_ship.vx = sin(player_ship.angle) * player_ship.thrust;
47 player_ship.vy = cos(player_ship.angle) * player_ship.thrust;
48
49 if(key[KEY_UP])
50 {
51 player_ship.x += player_ship.vx;
52 player_ship.y -= player_ship.vy;
53 }
54
55 if(key[KEY_LEFT])
56 player_ship.angle -= player_ship.turn_speed;
57
58 if(key[KEY_RIGHT])
59 player_ship.angle += player_ship.turn_speed;
60
61 if(key[KEY_SPACE])
62 player_fire();
63
64 if(bullet.empty() != true)
65 { // Snippet start
66 for(std::vector<Projectile>::iterator i = bullet.begin(); i != bullet.end(); i++)
67 {
68 i->x += i->vx;
69 i->y -= i->vy;
70 if(i->x > x_res || i->x < 0 || i->y > y_res || i->y < 0)
71 {
72 //This simply swaps the bullet that needs to be deleted
73 //with the last element in the vector and deletes it.
74 //Much faster than resizing the entire vector. Credit: Ceagon Xylas
75 if(i < bullet.size()- 1)
76 memcpy(&bullet<i>,&bullet.back(),sizeof(bullet));
77 bullet.pop_back();
78 }
79 }// Snippet end
80 }
81
82 speed_counter--;
83 }
84
85 for(int i = 0; i < bullet.size(); i++)
86 putpixel(buffer, (int)bullet<i>.x, (int)bullet<i>.y, makecol(255,255,255));
87
88 rotate_sprite(buffer, spaceship, (int)player_ship.x, (int)player_ship.y, ftofix(player_ship.angle*128.0/M_PI));
89 draw_sprite(screen, buffer, 0, 0);
90 clear_bitmap(buffer);
91 }
92
93 deinit();
94 return 0;
95} END_OF_MAIN()
96//////// Initialization ////////
97void init() {
98 int depth, res;
99 allegro_init();
100 depth = desktop_color_depth();
101 if (depth == 0) depth = 32;
102 set_color_depth(depth);
103 res = set_gfx_mode(GFX_AUTODETECT_WINDOWED, x_res, y_res, 0, 0);
104 if (res != 0) {
105 allegro_message(allegro_error);
106 exit(-1);
107 }
108 install_timer();
109 install_keyboard();
110
111 LOCK_VARIABLE(speed_counter);
112 LOCK_FUNCTION(increment_speed_counter);
113 install_int_ex(increment_speed_counter, BPS_TO_TIMER(60));
114
115 generate_332_palette(palette);
116 buffer = create_bitmap(x_res,y_res);
117 images = load_datafile("data.dat");
118 if(images == NULL)
119 {
120 set_gfx_mode(GFX_TEXT,0,0,0,0);
121 allegro_message("Could not load data.dat!");
122 exit(EXIT_FAILURE);
123 }
124 spaceship = (BITMAP*)images[spaceship_image].dat;
125
126 player_ship.angle = 0;
127 player_ship.x = x_res / 2;
128 player_ship.y = y_res / 2;
129 player_ship.turn_speed = 0.03;
130 player_ship.thrust = 2;
131}
132//////// Deinitialization ////////
133void deinit()
134{
135 clear_keybuf();
136 destroy_bitmap(buffer);
137 destroy_bitmap(spaceship);
138 unload_datafile(images);
139}
140//////// Projectile Firing ////////
141void player_fire()
142{
143 Projectile temp_bullet;
144 temp_bullet.speed = 2;
145 float spawn_distance = 11;
146 temp_bullet.vx = temp_bullet.speed * player_ship.vx;
147 temp_bullet.vy = temp_bullet.speed * player_ship.vy;
148 temp_bullet.x = (player_ship.x + spaceship->w/2) + spawn_distance * cos(player_ship.angle - 64.0/M_PI);
149 temp_bullet.y = (player_ship.y + spaceship->h/2) + spawn_distance * sin(player_ship.angle - 64.0/M_PI);
150
151 bullet.push_back(temp_bullet);
152}

Ceagon Xylas
Member #5,495
February 2005
avatar

Oh yuck, my brain totally pooped out on me. I mixed two ideas :P So lets rework this.

Here's the two ideas I was mixing:

if(!bullet.empty())
{
  for(std::vector<Projectile>::iterator i = bullet.begin(); i != bullet.end(); /*nothing here*/) 
  {
    i->x += i->vx;
    i->y -= i->vy;
    if(i->x > x_res || i->x < 0 || i->y > y_res || i->y < 0)
    {
      if(i!=bullet.end())
        bullet.erase(i);
    }
    else
      i++;
  }
}

And

1if(!bullet.empty())
2{
3 for(std::vector<Projectile>::iterator i = bullet.begin(); i != bullet.end(); /*nothing here*/)
4 {
5 i->x += i->vx;
6 i->y -= i->vy;
7 if(i->x > x_res || i->x < 0 || i->y > y_res || i->y < 0)
8 {
9 if(i!=bullet.end())
10 {
11 memcpy(&(*i),&bullet.back(),sizeof(Projectile));
12 bullet.pop_back();
13 }
14 }
15 else
16 i++;
17 }
18}

The last one's faster, but I'm not sure if it's buggy :-/ I'd advise using the first if it doesn't give you any performance issues.

Elvang
Member #7,828
October 2006
avatar

When it gets down to the last bullet in the vector the program now crashes with a "instruction at <insert address here> referenced memory at <insert address here>. The memory could not be 'read'." I'm guessing the iterator doesn't like all the erases?

EDIT: Nvm, every time I go to reply your post has been updated heh...

Yea, the second one crashes after 4 or 5 bullets exit the screen. Was about to say first one was also buggy until I noticed the increments location changed as well... Actually, the second one works now that I noticed that.

Ceagon Xylas
Member #5,495
February 2005
avatar

Hahaha I know, I'm such a lousy poster ::) I re-edit at least 20 times every post.

Elvang
Member #7,828
October 2006
avatar

So for everytime I need to cycle through a vector similar to this and possibly remove certain elements should I use that code?

Example of implementation for a lifespan for projectiles(Didn't want to repost entire source so I started at while and cut it off right after if(!bullet.empty())'s closing brace).

         while( speed_counter > 0)
         {
                if(!bullet.empty())
                {
                     for(std::vector<Projectile>::iterator i = bullet.begin(); i != bullet.end();)
                     {
                          i->lifetime++;
                          if(i->lifetime >= i->life_max)
                          {
                               memcpy(&(*i),&bullet.back(),sizeof(Projectile));
                               bullet.pop_back();
                          }
                          else
                               i++;
                     }
                }

EDIT: Nvm, just added in an extra check and variable increment to the existing for loop.

Ceagon Xylas
Member #5,495
February 2005
avatar

Yes, I'm pretty sure you could use that code every time you needed to find specific elements inside the vector and remove them. As long as the element meets the condition (in this case, i->lifetime>=i->maxlife) it'll be switched with the last element in the vector and then deleted.

By the way, if(!bullet.empty()) isn't necessary. If the vector's empty then i=bullet.end(), and the for loop will end immediately

Go to: