+1-855-211-0932 (ID:278472)
Not a customer yet? Sign up now!

HomeAnutechGodot fps controller tutorial

Godot fps controller tutorial

First on the presumption you have godot installed

Okay. First, you need Godot on your system. You can download for windows, linux, mac os, and you can use it for your server as it describes on the website
The server build is optimized to run dedicated game servers and does not include editor tools, graphics or audio support.
As of august 16, 2020, I am using linux lite. Cause fuck Microsoft. But anyway you should have it downloaded and since it's self contained, you should have an exe file to extract on windows, or a bin file that can be run in bash or if you use linux lite, you just have to right click and click on "mark as executable" and it would be a double click from there. Linux lite was designed to help people who are familiar with windows transition easily over to the open source beauty of linux (until the social justice warriors almost destroyed it!) Godot operates in nodes. Every component is a node, and the scene is more of a container. Think of nodes as arms, legs, head, brain, heart, veins etc, and Think of scene as what allows the components to go together like scene is the human body and nodes is the human parts that make it work, and function. Once you gotten godot downloaded open it up and start a new project. You will need barebones first. After you create a new project, you should see this screen:

Now from there click on the plus icon. That will allow you to add a node to the scene. From there in the box type in kinematic body. Then click on create on the bottom.

Now you notice that yellow exclamation mark by that kinematic body node you just created:
That is because it needs a collision shape (or collision polygon, depending on what your needs are) But you need to understand that for every kinematic body, rigid body, or static body, it will need a collision shape. So you will need to add that, and you can also add a mesh shape as well. That is more optional, but we will do it anyway. So click on the kinematic body node we just created to highlight it if it isn't highlighted already, then click on the plus button again to create a child node for the root node. Then type in the search box for collision shape and click on create.

That will create you a collision shape. Now we will make a shape for the collision. This will make a sphere shape that we will need. The shape we will use is the capsule shape. And we will rotate the collision shape 90 degrees on the rotation x axis. We will also need to set the scale values all to 1. x, y, z. In a 3d space there is 3 dimensions needed to make it up. Commonly referred to x, y, and z. 2d just has x (horizontal) and y (vertical). The z scale think of it as 'zoom' value. z for zoom. z scale for zoom in and out, or in 3d games, run forward or backwards. 3d Rotation has these values as well. So rotation x will be set manually to 90 in your godot editor.
To create the capsule shape as the shape we want for the collision shape, and set it's rotation x value to 90 degrees first you need to go onto the right side of the screen, where the inspect tab comes up when the collision shape node is selected (highlighted), click left on where it says shape. Click the drop down arrow, and select where it says New CapsuleShape click on it, and where it says transform on the right hand side where the inspector tab is

Now, we will need to set the radius and height of the capsule shape. We need to set the radius to 0.5 and it's height to 1.5.

Okay fellow derps, we did good so far. Now what we are going to do is construct a head and make it sort of "see". Well since it's a first person shooter controller, it will effectively be the camera we will see. Now we will highlight the kinematic body, and click the plus thingy again. This time with the search we will type in "Spatial". Spatial node is basically just one of the most barebones object (Node) in godot. Click create, and then rename the spatial node to "head" by double clicking on the spatial node. Then we are going to add a camera. So highlight the head node and click the plus button type "Camera" in the search box and click create. If need be, drag the camera node in the 3d space to the position of the camera or another way is in the transform in the inspect tab type in the same x,y, and z values as the head node.

As you can see, the nodes were placed in the center, and usually when the child node is created, it is in the same general area of the parent node. So we are going to drag the head and camera on top of the capsule collision shape. If you are asking what collision is in general, its how you create "contact" for the objects you create. Think of an example as one human touching another human persons arm with their index finger, and when the human lifts the finger, the other human's arm turns green in that spot.

See?
Now,

WE CAN START TO CODE IN GDScript

We will add a script to our First Person Controller for more advanced functionality, Movement, jump, etc... So first off, we will highlight the root node way at the top of the hierarchy. Then click on the little paper with a plus on the side to add a script, select the name of the gd script and click on create.

There's your gawddamned script!
Now we will go on from here to start the coding process. This requires math, so I will keep it as simple and not dull or cringe worthy as best as I can. If you want a video of better explanations, I will post a link to garbaj's youtube channel and can probably fill in the gap or whatever. To be effective in gdscript (or adapt easily to it) you need to at least know how to use the python programming language. I would start with learning python 3 and learn python 2 after 3 as neither that nor gdscript will be that much of an obstacle. GDScript language works like python as far as indentation and function definition goes. We won't get into too much about python. That shit will come later. It's a high-level language easily graspable for beginners (Noobs, YungBlud, neophyte, whatever..) I will only teach here enough to use godot. gdscript is propietary to Godot. Python is also object oriented so gdscript is object oriented-ish... Well, let me explain by comparison. In python to extend or include an external libraries, you would use #import in gdscript you use extends. Python


import external_kinematicBody

#function we write
def e_kb_collide(stream):
    print("collide")

python import
GDScript


extends KinematicBody

see? similar but different. Both would import things. Now we will need to set variables. Think of variables as things that contain data. Think of a cup of juice. The cup would play as a variable. What's in the cup? Orange juice.
#drink whatever is in the cup
def drink(liquid):
    print("drinking "+liquid)

#variable set
cup = "Orange Juice"

#call the code
drink(cup)
See you're drinking a cup of orange juice. Variables are just a name for a value to be used later. So in Godot we will set certain variables we need for the player.
var speed = 7
var acceleration = 20
var gravity = 9.3
var jump = 5

var mouse_sensitivity = 0.05

var static_keyboard_sensitivity = 0.08

Unlike python, in gdscript, you have to put the "var" keyword before the name of the variable and the value to make a variable. So.. you know... do that.

Now we will need to capture the head node within the script. The dollar sign will signify the node hierarchy that we want, effectively selecting the desired child or parent node. see?
#onready var for the head
onready var head = $hed



Now first what we want to do is make the camera follow our mouse. In order to do that one we will need to use the input event function. If you do anything like type on a keyboard, move a mouse, hook up some vr shit and do whatever with that, that is basically called an "Event". And there is a special function that we can use for the node script.
#a function is a block of code that is run when called.
#this will be how to declare and call a function for python
def da_function_call():
    print("function starting call")
    print("function end call")
    x = 40
    print("function is at "+40)


#call the function or make the function happen
da_function_call()
#call the function a second time
da_function_call()

If you installed python 3 just do a copy and paste and see that it will do what we wrote it to do. In python, to create a function you use the def keyword, the name of the function, and an open and close parenthesis. The parameters will go between the paranthesis. These are called parameters. These are variables needed for the function to work. Think of as an example of python way to do it.
def add_two_nums(a,b):
    return a + b

#parameters for the function
def add_da_numbas(a, b):
    #return types are set to send the code back up.
    #to return data is for example look here
    #number1 = 22
    #number2 = add_two_nums(11,11) 
    #number1 is 22
    #number2 = 22 because 11 + 11 = 22 and since the return value for the function is a + b and both a and b parameters are 11 it sends back the result which is 22.
    #this is how return works
    return a + b


print( add_da_numbas(11,2) ) # prints 13

a = 3 
b = 2
c = add_da_numbas(a, b)
print( "added numbas are "+c) # prints added numbas are 5

If this is confusing, or more understanding is needed, just look up youtube python programming tutorials and many people teach you more about python programming. Not short bursts like this. I am sure I will do it on this site later on, but for now, here we are. Youtube it (even though the current CEO is a feminist bigot that censors people that states facts based on actual science and data but whatever...) But back on track: We will need to process events in Godot. Now first we will need to set up the input function. How we will do this is as following:
#input like for mouse events or button controls
func _input(event: InputEvent) -> void:
	#if event is input event mouse motion or in lamens terms if the event that happens is the mouse being in motion or for the dumb fuckin tards if the mouse is moving
        #if mouse movement is detected

	if event is InputEventMouseMotion:
		#this will use the mouse to rotate the camemra horizontally. multiply how much the mouse is moved horizontally by the mouse sensitivity
                #how to rotate horizontally by the mouse movement
		rotate_y(deg2rad(-event.relative.x * mouse_sensitivity))
		#we will rotate on the y axis for the fps controller and convert degrees to radians while
                #we keep going along
                #we have the negative value on the horizontal x of the mouse event. -event.relative.x
                #from there, we multiply by the mouse sensitivity value
                # and since we are converting from degrees to radians it will be within the deg2rad function
                #and that value is applied to the rotate y axis

If you click on the play, it will ask you to save the scene. You will have to click yes, otherwise, you don't proceed so when you save, it will save by the name of the root node with a *.tscn extension, and then it will play, so if you move the mouse around, you'll see small rotation go on....
Also note that since this will be the only scene we have so far, we will for now set the fps controller as the main scene. The main scene is where the game will basically start at. You could set it to a credits scene, loading scene, etc... We will change the main scene later. So when it asks, just set the fps controller scene as the main scene and go from there. As you can see, it will still play.

See how the rotation slightly follows the mouse?
Now we will need to do much, much more. We will also look on the vertical rotation and also have the mouse cursor locked within your game. First thing is to set the vertical rotation, we do rotate_x instead of rotate_y. We would work on the y relative aspect of the event (vertical mouse directions), and under the ready function, this will be the code that will run as soon as the scene (Object) is loaded. And under the ready function, type in:
 Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) 
. Under the ready function, it is loaded as soon as the whole object is loaded. And as soon as the object is loaded, the mouse mode will be captured for use, only in the game. Click on play after you put in the code, and it will become clear. Go ahead and press Alt+Tab to switch out of that window, and close the window properly. We will code a work around for this later.

# Called when the node enters the scene tree for the first time.
func _ready():
	Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
	pass # Replace with function body.

#input like for mouse events or button controls
func _input(event: InputEvent) -> void:
	#if event is input event mouse motion or in lamens terms if the event that happens is the mouse being in motion or for the dumb fuckin tards if the mouse is moving
		#if mouse movement is detected
 
	if event is InputEventMouseMotion:
		#this will use the mouse to rotate the camemra horizontally. multiply how much the mouse is moved horizontally by the mouse sensitivity
				#how to rotate horizontally by the mouse movement
		rotate_y(deg2rad(-event.relative.x * mouse_sensitivity))
		#we will rotate on the y axis for the fps controller and convert degrees to radians while
				#we keep going along
				#we have the negative value on the horizontal x of the mouse event. -event.relative.x
				#from there, we multiply by the mouse sensitivity value
				# and since we are converting from degrees to radians it will be within the deg2rad function
				#and that value is applied to the rotate y axis
				
				
		#vertical rotation
		rotate_x(deg2rad(-event.relative.y * mouse_sensitivity))	



So, Now you'll notice your rotation going all way shot to shit. This is because rotating 3d axis is complicated at the best case scenario. You are going to need to keep the horizontal and vertical rotations separate, So to fix that here is what we need to do: change the rotate_x to head.rotate_x like so:
 head.rotate_x(deg2rad(-event.relative.y * mouse_sensitivity)) 


In the vertical rotation axis, we adjust the vertical rotation on the "head" node. This will keep them separate. Another fuckin' problem tho' : if we rotate vertical too far, it will go all far up side down. We sure as hell don't want that.

So to fix this, what we need to do is add a clamp. so by the following, we will add it to the head rotation. Clamp will only allow a value to go in each direction but by so far. In this case, we will only allow it to go up and down 90 degrees.
 head.rotation.x = clamp(head.rotation.x, deg2rad(-90),deg2rad(90)) 


After you click play, it should be good. See?

Now we need to move on....

Make the controller move and assign keys

Now, we set the variables on what we need to move, but not how. We will need to fix this. We will add a jump and WASD movement type for the controller. But first we will need to create a stage scene for using the movement and gravity and all that shit. So to create a test level so to speak, go to the menu bar on the top and click on scene, click on new scene, and a new scene will be generated.

Much of what we are going to do for our test level is basically the same thing as setting up our fps controller. So when the new scene is created, click the plus and Instead of making the root node of the level a kinematic body, we will use a static body. So type staticbody in the search bar, select the static body, and click on create. After that double click on the static body node in the hierarchy, and rename to testlevel and while it's highlighted, click on the plus again, to add a collision shape. In the inspect thingy, we will set it to a Box shape. In the transform menu under the inspect tab, we will set the scale of the platform. We will make the x and z values to 10 while setting the y value to 1. You can experiment as to why we will leave the y value at one. I'll leave that up to you. While the collisionshape is selected, click the plus so that way we create a child node of a mesh. Then in the search box, type meshinstance, and select the meshinstance, not the soft body, and not the CGImesh. Click create, and it will make it adjust to the collision shape of the parent node. After that, go over in the inspect side, and under the mesh tab, we will set it to be a cube mesh. After you do that, from the file explorer in the usual lower left hand side where all your files will be at, select your fps controller tscn object and drag and drop it over the platform in the 3d space. That will add the fps controller to the test level. It is now ready for testing....

But as you notice, when you click the play button, it will still bring up the fps object we made. It is not the test level. This is because earlier, we set the fps controller as the main scene. We will need to set the testlevel.tscn as the main scene so to do this we will go to, project at the top, select project settings, under application tab in the first tab, we will go over from config to run. and set the main scene to testlevel by clicking the folder, and clicking the testlevel.tscn and proceed from there. Close the menu out, and click play and you should be on the test level with your fps controller. See?

Now we are ready to code movement and gravity. Now we will create a _process function. This function will update every frame. So if the game (for some reason ) is at 15 frames per second (fps abbr. is already used for first person shooter) , the code under the _process function will run 15 times each second. That is the best way to explain the use of the _process code for Godot.
func _process(delta:float)-> void: 
Now the func is the keyword for Godot to identify a function. _process is the function name, delta is the variable parameter. Now you see that float by delta separated by a colon. That float is the data type going into the parameters. Some programming languages make you set the data type for the parameter this way. I studied 14 different programming languages, so I know. and that arrow to the void, that is the return type that is expected. Void does not have a data type associated with it. So therefore a return value is not required for this function. If it were a float, or an int, it would have to have a return value as said float or int. If you tried to return a float value for a int return type, it would fail. As a side note, if you tried to return any value with a void return type, it wouldn't work, because void is not a data type. It's only there for functions that don't send data results back.
So if you'll follow along, we are typing the _process function, it should be there already. Now we will need to set the keys to use for moving and jumping. In Godot, you will need to go to project, and project settings. Move over to the input Map tab, and set the values for the w, a, s, d, and space bar buttons for use in the game.
in the input map menu in the box, it will be the label of what the button will be
so if you put in punch in the box and click add, it will add punch as a function, and
if you click the plus button by the punch label, it will give you the option to set
a keyboard key, joystick button, mainstream game button, or mouse click. when the
popup comes up, press the button or key you want associated with the punch function and
click on okay after it captures the requested key. click ok, and your button is set. You can 
set multiple keys to do that one action.

so move_forward = w
move_backward =s
move_left = a
move_right = d
fukin_jump = spacebar

got it?

side note: you will see pass under some functions. Since pass is also a python 3 syntax I will use python code to explain what exactly pass means:
#pass is used to fill in a function
#to not trigger an illegal end of file error
#a function's body has to be filled in,
#otherwise it won't properly terminate
#so pass can be used to run an empty function
#without throwing bad errors
#cause technically it isn't empty
#but there is nothing in there either
#--python --function
def a_term_test():
    pass
#--godot--function
func a_term_test()->void:
    pass

#see that, both functions are even callable, even though there is nothing in it. 


Now to further expand on this tutorial, were going to release the mouse capture when we press the escape key. If you didn't notice in the input map within the project settings, ui_cancel was set to the escape key on the keyboard. So in code
#so in short if escape key has just been pressed,
if Input.is_action_just_pressed("ui_cancel"):
                #we release the window's hold on the mouse
		Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)


Now that is how the basics of how we will make the fps controller move and shit. First thing to do is under the process function we set move_forward and move_backward. This will be selective by if and elif and stuff.
if Input.is_action_pressed("move_forward"):
    direction -= transform.basis.z

elif Input.is_action_pressed("move_backward"):
    direction += transform.basis.z

That means if we press the move_forward button ("w") , we move forward. elif (short for else if ) we are moving backward we move back. See direction variable earlier was set as a vector 3 or:
 var direction = Vector3()
Vector math is beyond the scope of this tutorial so we will only discuss the only parts that are important about it here. Vector3 is just a shorthand notation for the x, y, z values to return. Like Vector2() would be for just x, y as that is basically numbers for 2d and 3d. We are doing 3d so Vector3 is where our focus will be. so the variable will be direction set to 3d numbers (Vector3) so in short, 3d numbers. That is what Vector3 is. Now that is out of the way, let's get to the functioning of the movement.

direction -= transform.basis.z

transform if you remember on the inspect tab it has rotation, scale, and translation. Scale does the stretching of an object, rotation handles the rotation, and transform handles where in the 3d space it is or going. so in short the basis is giving the general position. and the 'z' in the transform.basis.z is the "zoom" factor. so when we set the direction to be currently subtracted in the z value of the transform.
we will set all the necessary values.
 if Input.is_action_pressed("move_forward"):
    direction -= transform.basis.z

elif Input.is_action_pressed("move_backward"):
    direction += transform.basis.z

if Input.is_action_pressed("move_left"):
    direction -= transform.basis.x

elif Input.is_action_pressed("move_right"):
    direction += transform.basis.x

elif makes sure that you can't go left and right at the same time. Now check it: set the direction under 3d numbers, and then manipulate the basis of the object when the commands serve. This is what we will be doing. This will go into godot. first declare the direction variable at the top with the other variables
var direction = Vector3()

Note: declaring a variable or function means to initialize or start with. defining a variable or function is it's definition. what does it do? what it does, that defines it. Compare declaring a variable to a human being's name, and defining as that same human being's personality.
put the following code into the _process function:


	if Input.is_action_pressed("move_forward"):
		direction -= transform.basis.z

	elif Input.is_action_pressed("move_backward"):
		direction += transform.basis.z

	if Input.is_action_pressed("move_left"):
		direction -= transform.basis.x

	elif Input.is_action_pressed("move_right"):
		direction += transform.basis.x




Now you click play, and it doesn't work. What gives? All we did was tell the game where we want to go, but not how. we will fix that now:

#so that the object doesn't go faster when we go 
#diagonally, we will normalize the direction

direction = direction.normalized()

move and slide function will be the one to apply physics and gravity and all that shit to the object it'self. This is how we will use it.
#apply movement and physics 
move_and_slide(direction * speed, Vector3.UP)

Vector3 move_and_slide(linear_velocity: Vector3, up_direction: Vector3 = Vector3( 0, 0, 0 ), stop_on_slope: bool = false, max_slides: int = 4, floor_max_angle: float = 0.785398, infinite_inertia: bool = true)
move and slide according to the docs, moves the body along a vector. If the body collides with another, it will slide along the other body rather than stop immediately. If the other body is a KinematicBody or RigidBody, it will also be affected by the motion of the other body. You can use this to make moving or rotating platforms, or to make nodes push other nodes.
now don't think that just typing that in is going to cut it. No it won't. If you typed in the code already and clicked play, you'll slide and won't ever stop.
to fix that and apply more realistic movement first we need to reset the direction back to 0 each frame.
func _process(delta):
  #place this at the way top
  direction = Vector3() #sets back to 0 each frame

and to make it move more realistically, you need to put this information on the way bottom under the direction.normalized() we will have to set the Velocity in the global scope. The explanation seems complex but it really isn't. Cause To make it brief the global scope is zero tabs. If you have a document and you start at line and column one, you press tab you go 4 spaces. you go back 4 spaces to get to 0, back to the starting point, you go to the global scope. In python and godot, this is important. If any line of code is not properly indented, it will crash. Python is interpreted and godot works just like python.
 
#global scope
#declare Velocity
var velocity = Vector3()
var mouse_sensitivity = 0.05

var direction = Vector3()
var fall = Vector3()

func _process(delta: float)->void:
   #local scope
   pass
Now we can work with the velocity. Vector3.liner_interpolate is basically doing smooth transitioning. basic definition:
Vector3 linear_interpolate(b: Vector3, t: float)
linear_interpolate basically returns the result of the linear interpolation between this vector and b by amount t. t is in the range of 0.0 - 1.0, representing the amount of interpolation... Look at how it happened.
direction = direction.normalized()
velocity = velocity.linear_interpolate(direction * speed, acceleration * delta)

velocity = move_and_slide(velocity, Vector3.UP)

Note:

Vector3.UP would set the gravity seed as what direction would be up for the gravity variable. Think this isn't important, I promise you your shit won't work like you wanted it to. I learned this the hard way.


now we will need to set the gravity and jump for the final step. you need to set the fall variable as a Vector3() as well. And place it on top outside of all the scopes where the other variables are declared.
#falling factor
var fall = Vector3()

Now you will need to set that up. Do it under the direction on the global scope at the way top where jum and speed variables have been declared. Look at dat skween:


func _process(delta: float)->void:
    direction = Vector3()

     #make us fall if we aren't on the ground
    if not is_on_floor():
        fall.y -= gravity * delta

    #make us jump
    if Input.is_action_just_pressed("fukin_jump"):
        fall.y = jump

To make the gravity actually pull, you will need to place another move and slide code underneath velocity
velocity = move_and_slide(velocity, Vector3.UP)
#make the gravity happen
move_and_slide(fall, Vector3.UP)

thaathhh how ittthhh dunnne!!!

after we declare everything, we finish the nice touchups with the complex(ish) stuff

your script finalized should look like this



extends KinematicBody

var speed = 7
var acceleration = 20
var gravity = 9.3
var jump = 5
 
var velocity = Vector3()
var mouse_sensitivity = 0.05
 
var static_keyboard_sensitivity = 0.08
# Declare member variables here. Examples:
# var a = 2
# var b = "text"



#the direction where we will be going
var direction = Vector3()
#we need to fall
var fall = Vector3()




onready var head = $hed
# Called when the node enters the scene tree for the first time.
func _ready():
	Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
	pass # Replace with function body.

#input like for mouse events or button controls
func _input(event: InputEvent) -> void:
	#if event is input event mouse motion or in lamens terms if the event that happens is the mouse being in motion or for the dumb fuckin tards if the mouse is moving
		#if mouse movement is detected
 
	if event is InputEventMouseMotion:
		#this will use the mouse to rotate the camemra horizontally. multiply how much the mouse is moved horizontally by the mouse sensitivity
				#how to rotate horizontally by the mouse movement
		rotate_y(deg2rad(-event.relative.x * mouse_sensitivity))
		#we will rotate on the y axis for the fps controller and convert degrees to radians while
				#we keep going along
				#we have the negative value on the horizontal x of the mouse event. -event.relative.x
				#from there, we multiply by the mouse sensitivity value
				# and since we are converting from degrees to radians it will be within the deg2rad function
				#and that value is applied to the rotate y axis
				
				
		#vertical rotation
		head.rotate_x(deg2rad(-event.relative.y * mouse_sensitivity))	
		head.rotation.x = clamp(head.rotation.x, deg2rad(-90),deg2rad(90))
		
		
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta:float)-> void:
	#reset the direction to 0
	direction = Vector3()
	
	#we will fall if we are not on the ground
	if not is_on_floor():
		fall.y -= gravity * delta
		
	#make us jump
	if Input.is_action_just_pressed("fukin_jump"):
		fall.y = jump	
	#we will set the mouse capture mode to off
	if Input.is_action_just_pressed("ui_cancel"):
		Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
		
	if Input.is_action_pressed("move_forward"):
		direction -= transform.basis.z
		
	elif Input.is_action_pressed("move_backward"):
		direction += transform.basis.z
		
	if Input.is_action_pressed("move_left"):
		direction -= transform.basis.x
		
	if Input.is_action_pressed("move_right"):
		direction += transform.basis.x		
				
	direction = direction.normalized()
	velocity = velocity.linear_interpolate(direction * speed,
	acceleration * delta)
	velocity = move_and_slide(velocity, Vector3.UP)
	
	#make gravity happen
	move_and_slide(fall, Vector3.UP)	


Once done, Your controller should be functioning without any problems. Now that you know how to develop a fps controller, the possibilities are endless. I will post more as I work on my next game. Stay educated and remember "GET WOKE, GO BROKE!!!" cause if you're "woke", you're literally a bigot and you need some help sorting out the hate in your hearts.

Tags:

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>


Anutech Technical Resources

0