Join us on Discord!
You can help CodeWalrus stay online by donating here.

How do you make String Images Ti Nspire Lua

Started by semiprocoder, September 27, 2015, 03:31:24 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Adriweb

Indeed, try yourself and if it's good enough for you then...
I shall remind you that using older apilevels will make you unable to use newer APIs (and there have been a bunch, since apilevel 2.0)
(A solution would be to make two separate tns, one for 3.1 with string images, and one for later, with image resources and usages of new APIs if needed)

Regarding classes, the syntax is described in the manual, for instance. It's SubClass = class(ParentClass). Make sure you init the parent class too. Here's an example of just that : https://github.com/TI-Planet/ETK/blob/5074f85fc7acc3d14a9f3fc1a4295d2b460dd8bf/ETK/widgetmanager.lua#L7-L17
  • Calculators owned: TI-Nspire CX CAS, TI-Nspire CX, TI-Nspire CAS (x3), TI-Nspire (x2), TI-Nspire CM-C CAS, TI-Nspire CAS+, TI-80, TI-82 Stats.fr, TI-82 Plus, TI-83 Plus, TI-83 Plus.fr USB, TI-84+, TI-84+ Pocket SE, TI-84+ C Silver Edition, TI-84 Plus CE, TI-89 Titanium, TI-86, TI-Voyage 200, TI-Collège Plus, TI-Collège Plus Solaire, 3 HP, some Casios
Co-founder & co-administrator of TI-Planet and Inspired-Lua

LD Studios

#16
Quote from: Adriweb on September 28, 2015, 03:14:31 PM
If you use images extensively (especially if big), you WILL feel the difference between OSes. Benchmarks don't always reflect reality... A simple test is using mViewer GX Creator in API level 1.0 (string images) and in API level 2.3+ (resources images) ;)
I think another big difference is the loading time, in favor of the image resources. But I don't have timings for that.
I understand that the difference between OSes and image drawing is significant, but from what I've seen using image strings on newer OSes doesn't seem particularly different than using image resources with them. That being said, I suppose the difference in speed could be more drastic if you "use images extensively (especially if big)".

Quote from: Adriweb on September 28, 2015, 03:14:31 PM
Quote-Image data embedded directly in the code makes transferring code between editors and people much easier
Sure, but needing to do that pretty much never happened to me, in the past few years... Use external editors and building scripts :P (However older Luna versions didn't support the new image resource system)
This may not be especially relevent to you, but I've run into the issue fairly often, especially when collaborating with others, or if coding on calc (in which case there is no way to use image resources).

Quote from: Adriweb on September 28, 2015, 03:14:31 PM
Quote-Image strings can be manipulated to manually reverse images, or replace colors within an image
True, though these techniques aren't extensively used.
These techniques might not be extensively used, but I'm not sure why not, because there is a lot we can do with them. I particularly mentioned this because a project I was working on a few months ago involved a small library to manipulate image strings in such ways, and I found it very useful.

Anyways, I wasn't trying to say you arguments were totally invalid, but I'm just trying to point out that there are plenty of arguments in each direction, and I definitely don't think that one method can be ruled out as worse than the other.



semiprocoder

Quote from: Adriweb on September 28, 2015, 08:59:28 PM
Indeed, try yourself and if it's good enough for you then...
I shall remind you that using older apilevels will make you unable to use newer APIs (and there have been a bunch, since apilevel 2.0)
(A solution would be to make two separate tns, one for 3.1 with string images, and one for later, with image resources and usages of new APIs if needed)

Regarding classes, the syntax is described in the manual, for instance. It's SubClass = class(ParentClass). Make sure you init the parent class too. Here's an example of just that : https://github.com/TI-Planet/ETK/blob/5074f85fc7acc3d14a9f3fc1a4295d2b460dd8bf/ETK/widgetmanager.lua#L7-L17

Yeah I did that, but it still didn't have one of the methods that I use. Also what does the "do" keyword do in front of the classes?

Anyways, here is my code(I will probably start another thread for this but for  now eh). The error appears on line 105 and it sais that draw was not defined:

--[[
for now I will just have premade little chunk thingies so that I dont have to worry about map generation
each chunk is 8 by 8
water chunks(ones that have water in the map) will be bigger and have more variability\

spawn for each player is 24 by 24 blocks and is separate from chunks
maybe a few selections for this

buildings can be placed onto grass or sand
g=grass subscripts could be a, b, c, d, e, or f(just different textures
t=tree lowercase or uppercase because it is part of terrain but it is
s=sand
w=water
s=space
Ores are capital so as to not break with the rest of the stuff
I=iron
G=gold
F=food(forage food)
O=oil
S=stone
C=copper
T=tree
that makes 7 resources
order is: F, T, S, C, I, G, O
drawString is reversed, doesnt seem anything else is though
]]
local screen = platform.window
local screenWidth=screen:width()
local screenHeight=screen:height()
bridge1 = image.new(_R.IMG.grass1)

local startX=1
local startY=1
local endX=startX+screenWidth/15
local endY=startY+screenHeight/15
local dispSizeX=endX-startX
local dispSizeY=endY-startY

local mapSize=40

local floor=math.floor

--[[function drawSprite(gc, x, y, endX, endY, sprite, size)--draws an array of pixels to the screen. doesnt work properly for now if rescaled size is not exact multiple of size
local actualSize=endX-x
local ratio=actualSize/size
ratio=floor(ratio)--easier to deal with than fractional rescales
for i=0,i<size do

end
end]]

local baseUnit=class()
function baseUnit:draw()
end

local baseTile=class()
--will have it be images for a tile if a boolean value is true or something
baseTile.redClr=0xFF
baseTile.blueClr=0x00
baseTile.greenClr=0x00
baseTile.mineable=false--if a unit can harvest it
baseTile.walkable=true--if a unit can walk through it.
baseTile.boatsOnly=false--if water essentially
baseTile.shallow=true--in early ages some boats can only go into shallow water
baseTile.hill=false--if hilly. Just for asthetics if I manage to make it in
baseTile.hillDeg=0--if deg<70 degrees any unit can walk. otherwise only special units can walk over
baseTile.mineID=0--only used if mineable. Id of the item that you're mining, like iron will have some id
baseTile.mineSpeed=0--also only used if mineable
baseTile.mineLeft=0--amount of resource left in mine
baseTile.mineFull=0--amount of resources if tile is full
function baseTile:draw(gc, X, Y)
local tempX=X-startX
local tempY=Y-startY
gc:setColorRGB(self.redClr, self.blueClr, self.greenClr)
gc:fillRect(screenWidth/(endX-startX)*tempX, screenHeight/(endY-startY)*tempY, screenWidth/(endX-startX), screenHeight/(endY-startY))
end
function baseTile:citizen()--if citizen walks to it
end
function baseTile:newTile(r, g, b, mineable, walkable, boatsOnly, shallow, hill, hillDeg, mineID, mineSpeed, mineFull)
local tmp=class(self)
tmp.redClr=r
tmp.greenClr=g
tmp.blueClr=b
tmp.mineable=mineable
tmp.walkable=walkable
tmp.boatsOnly=boatsOnly
tmp.shallow=shallow
tmp.hill=hill
tmp.hillDeg=hillDeg
tmp.mineID=mineID
tmp.mineSpeed=mineSpeed
tmp.mineLeft=mineStart
tmp.mineFull=mineFull
end

local mapHandler=class()
mapHandler.map={}
mapHandler.mapSize=32
mapHandler.grass=baseTile:newTile(50, 255, 0, false, false, false, false, false, 0, 0, 0, 0)
mapHandler.dirt=baseTile:newTile(50, 25, 0, false, false, false, false, false, 0, 0, 0, 0)
mapHandler.index={grass, dirt}
function mapHandler:drawMap(gc)
for i=startX, endX do
for j=startY, endY do
self.map[i][j]:draw(gc, i, j)
end
end
end
function mapHandler:generate()--generates the map
for i=1,32 do
self.map[i]={}
for j=1, 32 do
self.map[i][j]=class(self.index[(i+j)%2])--for now a very simple generation just for testing purposes
end
end
end




Chunks={}
Map={}
Buildings={}
Units={} -- first element in subarray(per unit): id, then epoch. Each epoch has separate ids but epoch makes it faster to find the unit. Then coords. Then health. Then, if needed, array for projectile coords
--they also have unit vector for where they are facing
--stats are located in epoch area to minimize memory impact
--each unit type like fror example archer or something has an id but then each unit has a subid independent of that that has the number in the array they are in
--when a unit dies that array space becomes open and then placed into another array that has all the free spaces and the free space just after the highest id unit
--when a projectiles locks on it locks onto an id and if its coords are the same as within some margin of the id unit's coords, it hits that unit
Epoch=0
GameStart=false
Resources={0,0,0,0,0,0,0}


function displayMap()
end

--bridge1 = image.copy(bridge2, 2.0 * image.width(bridge2), 2.0 * image.height(bridge2))
function fastSin(Radians)
end
function fastCos(Radians)
    --return fastSin(Radians-pi/2)
end
function on.construction()

timer.start(1/60)

end

function on.timer()

screen:invalidate()

end

function on.arrowUp()

sc = (var.recall("scale") or 0.5)

var.store("scale", sc + 0.1)

screen:invalidate()

end

function on.arrowDown()

screen:invalidate()
end

function on.paint(gc)
mapHandler:generate()
mapHandler:drawMap(gc)

end
  • Calculators owned: ti nspire, ti 84 plus se
My cemetech username is awesommee333.

Adriweb

The 'do' ... 'end' are to create, in this case, a temporary local scope where the local variables defined inside won't be global, which they would be if the do ... end wasn't there, since they're located out of any functions.

Regarding your code, I haven't look much but at least I can see that you're missing the :init(...) methods for each class.
Go look up some examples ;) (same link works)
  • Calculators owned: TI-Nspire CX CAS, TI-Nspire CX, TI-Nspire CAS (x3), TI-Nspire (x2), TI-Nspire CM-C CAS, TI-Nspire CAS+, TI-80, TI-82 Stats.fr, TI-82 Plus, TI-83 Plus, TI-83 Plus.fr USB, TI-84+, TI-84+ Pocket SE, TI-84+ C Silver Edition, TI-84 Plus CE, TI-89 Titanium, TI-86, TI-Voyage 200, TI-Collège Plus, TI-Collège Plus Solaire, 3 HP, some Casios
Co-founder & co-administrator of TI-Planet and Inspired-Lua

semiprocoder

Well, I tried, but it didn't work whatsoever. I am probably doing something wrong because I didn't fully understand how to do it with that example. Anyways, can you show me what I am doing wrong? Now it crashes when I try to init the map:


--[[
for now I will just have premade little chunk thingies so that I dont have to worry about map generation
each chunk is 8 by 8
water chunks(ones that have water in the map) will be bigger and have more variability\

spawn for each player is 24 by 24 blocks and is separate from chunks
maybe a few selections for this

buildings can be placed onto grass or sand
g=grass subscripts could be a, b, c, d, e, or f(just different textures
t=tree lowercase or uppercase because it is part of terrain but it is
s=sand
w=water
s=space
Ores are capital so as to not break with the rest of the stuff
I=iron
G=gold
F=food(forage food)
O=oil
S=stone
C=copper
T=tree
that makes 7 resources
order is: F, T, S, C, I, G, O
drawString is reversed, doesnt seem anything else is though
]]
local screen = platform.window
local screenWidth=screen:width()
local screenHeight=screen:height()
bridge1 = image.new(_R.IMG.grass1)

local startX=1
local startY=1
local endX=startX+screenWidth/15
local endY=startY+screenHeight/15
local dispSizeX=endX-startX
local dispSizeY=endY-startY

local mapSize=40

local floor=math.floor

--[[function drawSprite(gc, x, y, endX, endY, sprite, size)--draws an array of pixels to the screen. doesnt work properly for now if rescaled size is not exact multiple of size
local actualSize=endX-x
local ratio=actualSize/size
ratio=floor(ratio)--easier to deal with than fractional rescales
for i=0,i<size do

end
end]]

local baseUnit=class()
function baseUnit:draw()
end

local baseTile=class()
--will have it be images for a tile if a boolean value is true or something
baseTile.redClr=0xFF
baseTile.blueClr=0x00
baseTile.greenClr=0x00
baseTile.mineable=false--if a unit can harvest it
baseTile.walkable=true--if a unit can walk through it.
baseTile.boatsOnly=false--if water essentially
baseTile.shallow=true--in early ages some boats can only go into shallow water
baseTile.hill=false--if hilly. Just for asthetics if I manage to make it in
baseTile.hillDeg=0--if deg<70 degrees any unit can walk. otherwise only special units can walk over
baseTile.mineID=0--only used if mineable. Id of the item that you're mining, like iron will have some id
baseTile.mineSpeed=0--also only used if mineable
baseTile.mineLeft=0--amount of resource left in mine
baseTile.mineFull=0--amount of resources if tile is full
function baseTile:draw(gc, X, Y)
local tempX=X-startX
local tempY=Y-startY
gc:setColorRGB(self.redClr, self.blueClr, self.greenClr)
gc:fillRect(screenWidth/(endX-startX)*tempX, screenHeight/(endY-startY)*tempY, screenWidth/(endX-startX), screenHeight/(endY-startY))
end
function baseTile:citizen()--if citizen walks to it
end
function baseTile:init(r, g, b, mineable, walkable, boatsOnly, shallow, hill, hillDeg, mineID, mineSpeed, mineFull)
self.parent=parent
self.redClr=r
self.greenClr=g
self.blueClr=b
self.mineable=mineable
self.walkable=walkable
self.boatsOnly=boatsOnly
self.shallow=shallow
self.hill=hill
self.hillDeg=hillDeg
self.mineID=mineID
self.mineSpeed=mineSpeed
self.mineLeft=mineStart
self.mineFull=mineFull
self.children={}
end
function baseTile:newTile()
local tmp=class(self)
tmp.redClr=r
tmp.greenClr=g
tmp.blueClr=b
tmp.mineable=mineable
tmp.walkable=walkable
tmp.boatsOnly=boatsOnly
tmp.shallow=shallow
tmp.hill=hill
tmp.hillDeg=hillDeg
tmp.mineID=mineID
tmp.mineSpeed=mineSpeed
tmp.mineLeft=mineStart
tmp.mineFull=mineFull
end
function baseTile:clone()
   local tmp=class(self)
   tmp.init(self.redClr, self.greenClr, self.blueClr, self.walkable, self.mineable, self.boatsOnly, self.shallow, self.hill, self.hillDeg, self.mineID, self.mineSpeed, self.mineLeft, self.mineFull)
   return tmp
end

local mapHandler=class()
mapHandler.map={}
mapHandler.mapSize=32
mapHandler.grass=class(baseTile):init(50, 255, 0, false, false, false, false, false, 0, 0, 0, 0)
mapHandler.dirt=class(baseTile):init(50, 25, 0, false, false, false, false, false, 0, 0, 0, 0)
mapHandler.index={grass, dirt}
function mapHandler:drawMap(gc)
for i=startX, endX do
for j=startY, endY do
self.map[i][j]:draw(gc, i, j)
end
end
end
function mapHandler:init()
   self:generate()
end
function mapHandler:generate()--generates the map
for i=1,32 do
self.map[i]={}
for j=1, 32 do
self.map[i][j]=self.index[(i+j)%2]:clone()--for now a very simple generation just for testing purposes
end
end
end




Chunks={}
Map={}
Buildings={}
Units={} -- first element in subarray(per unit): id, then epoch. Each epoch has separate ids but epoch makes it faster to find the unit. Then coords. Then health. Then, if needed, array for projectile coords
--they also have unit vector for where they are facing
--stats are located in epoch area to minimize memory impact
--each unit type like fror example archer or something has an id but then each unit has a subid independent of that that has the number in the array they are in
--when a unit dies that array space becomes open and then placed into another array that has all the free spaces and the free space just after the highest id unit
--when a projectiles locks on it locks onto an id and if its coords are the same as within some margin of the id unit's coords, it hits that unit
Epoch=0
GameStart=false
Resources={0,0,0,0,0,0,0}


function displayMap()
end

--bridge1 = image.copy(bridge2, 2.0 * image.width(bridge2), 2.0 * image.height(bridge2))
function fastSin(Radians)
end
function fastCos(Radians)
    --return fastSin(Radians-pi/2)
end
function on.construction()

timer.start(1/60)

end

function on.timer()

screen:invalidate()

end

function on.arrowUp()

sc = (var.recall("scale") or 0.5)

var.store("scale", sc + 0.1)

screen:invalidate()

end

function on.arrowDown()

screen:invalidate()
end

function on.paint(gc)
mapHandler:generate()
mapHandler:drawMap(gc)

end
  • Calculators owned: ti nspire, ti 84 plus se
My cemetech username is awesommee333.

semiprocoder

Anyone have any solutions, cause that would really help for my code cause I will use it for pretty much everything. I just made an init function but Im not really sure what to do now.
  • Calculators owned: ti nspire, ti 84 plus se
My cemetech username is awesommee333.

Adriweb

You're calling the class constructor badly.
The init is the constructor, and you just have to do local myInstance = MyClass(arg1, arg2);
"never" call init(...) directly
Also you assign properties to the classes instead of the instances, that's rather unusual. Stuff like this /should/ be in the init, with self.x = ... etc.

I suggest you look at existing scripts using classes, you'll see that it's simpler than what you've done, but you're probably not very far.
  • Calculators owned: TI-Nspire CX CAS, TI-Nspire CX, TI-Nspire CAS (x3), TI-Nspire (x2), TI-Nspire CM-C CAS, TI-Nspire CAS+, TI-80, TI-82 Stats.fr, TI-82 Plus, TI-83 Plus, TI-83 Plus.fr USB, TI-84+, TI-84+ Pocket SE, TI-84+ C Silver Edition, TI-84 Plus CE, TI-89 Titanium, TI-86, TI-Voyage 200, TI-Collège Plus, TI-Collège Plus Solaire, 3 HP, some Casios
Co-founder & co-administrator of TI-Planet and Inspired-Lua

semiprocoder

So take two. I looked at some example code, but it was simple and my code still doesn't work. I made the init class(and you don't need the parent thingy right cause that was in the code you showed me before but it looks unnecessary and isn't in other codes, but I just have it for now), and I got an instance of it using baseTile(), and right now I don't even use the index; I just set everything to the grass tile. Now it seems it still doesnt initialize, and the draw function is not present and doesnt work. Sorry for being such a n00b, but eventually maybe I'll learn ;)

Anyways here is the newer code:

--[[
for now I will just have premade little chunk thingies so that I dont have to worry about map generation
each chunk is 8 by 8
water chunks(ones that have water in the map) will be bigger and have more variability\

spawn for each player is 24 by 24 blocks and is separate from chunks
maybe a few selections for this

buildings can be placed onto grass or sand
g=grass subscripts could be a, b, c, d, e, or f(just different textures
t=tree lowercase or uppercase because it is part of terrain but it is
s=sand
w=water
s=space
Ores are capital so as to not break with the rest of the stuff
I=iron
G=gold
F=food(forage food)
O=oil
S=stone
C=copper
T=tree
that makes 7 resources
order is: F, T, S, C, I, G, O
drawString is reversed, doesnt seem anything else is though
]]
local screen = platform.window
local screenWidth=screen:width()
local screenHeight=screen:height()
bridge1 = image.new(_R.IMG.grass1)

local startX=1
local startY=1
local endX=startX+screenWidth/15
local endY=startY+screenHeight/15
local dispSizeX=endX-startX
local dispSizeY=endY-startY

local mapSize=40

local floor=math.floor


--[[function drawSprite(gc, x, y, endX, endY, sprite, size)--draws an array of pixels to the screen. doesnt work properly for now if rescaled size is not exact multiple of size
local actualSize=endX-x
local ratio=actualSize/size
ratio=floor(ratio)--easier to deal with than fractional rescales
for i=0,i<size do

end
end]]

local baseUnit=class()
function baseUnit:draw()
end

local baseTile=class()
--will have it be images for a tile if a boolean value is true or something
--baseTile.redClr=0xFF
--baseTile.blueClr=0x00
--baseTile.greenClr=0x00
--baseTile.mineable=false--if a unit can harvest it
--baseTile.walkable=true--if a unit can walk through it.
--baseTile.boatsOnly=false--if water essentially
--baseTile.shallow=true--in early ages some boats can only go into shallow water
--baseTile.hill=false--if hilly. Just for asthetics if I manage to make it in
--baseTile.hillDeg=0--if deg<70 degrees any unit can walk. otherwise only special units can walk over
--baseTile.mineID=0--only used if mineable. Id of the item that you're mining, like iron will have some id
--baseTile.mineSpeed=0--also only used if mineable
--baseTile.mineLeft=0--amount of resource left in mine
--baseTile.mineFull=0--amount of resources if tile is full
function baseTile:draw(gc, X, Y)
local tempX=X-startX
local tempY=Y-startY
gc:setColorRGB(self.redClr, self.blueClr, self.greenClr)
gc:fillRect(screenWidth/(endX-startX)*tempX, screenHeight/(endY-startY)*tempY, screenWidth/(endX-startX), screenHeight/(endY-startY))
end
function baseTile:citizen()--if citizen walks to it
end
function baseTile:init()
   self.parent=parent
   self.children={}
end
function baseTile:init(r, g, b, mineable, walkable, boatsOnly, shallow, hill, hillDeg, mineID, mineSpeed, mineFull)
self.parent=parent
self.redClr=r
self.greenClr=g
self.blueClr=b
self.mineable=mineable
self.walkable=walkable
self.boatsOnly=boatsOnly
self.shallow=shallow
self.hill=hill
self.hillDeg=hillDeg
self.mineID=mineID
self.mineSpeed=mineSpeed
self.mineLeft=mineStart
self.mineFull=mineFull
self.children={}
end
function baseTile:newTile()
local tmp=class(self)
tmp.redClr=r
tmp.greenClr=g
tmp.blueClr=b
tmp.mineable=mineable
tmp.walkable=walkable
tmp.boatsOnly=boatsOnly
tmp.shallow=shallow
tmp.hill=hill
tmp.hillDeg=hillDeg
tmp.mineID=mineID
tmp.mineSpeed=mineSpeed
tmp.mineLeft=mineStart
tmp.mineFull=mineFull
end
function baseTile:clone()
   local tmp=self(self.redClr, self.greenClr, self.blueClr, self.walkable, self.mineable, self.boatsOnly, self.shallow, self.hill, self.hillDeg, self.mineID, self.mineSpeed, self.mineLeft, self.mineFull)
   --tmp.init(self.redClr, self.greenClr, self.blueClr, self.walkable, self.mineable, self.boatsOnly, self.shallow, self.hill, self.hillDeg, self.mineID, self.mineSpeed, self.mineLeft, self.mineFull)
   return tmp
end

local mapHandler=class()
function mapHandler:drawMap(gc)
for i=startX, endX do
for j=startY, endY do
self.map[i][j]:draw(gc, i, j)
end
end
end
function mapHandler:init()
   self.parent=parent
   self.map={}
   self.mapSize=32
   self.grass=baseTile(50, 255, 0, false, false, false, false, false, 0, 0, 0, 0)
   self.dirt=baseTile(50, 25, 0, false, false, false, false, false, 0, 0, 0, 0)
   self.index={grass, dirt}
   self:generate()
end
function mapHandler:generate()--generates the map
for i=1,32 do
self.map[i]={}
for j=1, 32 do
self.map[i][j]=self()--for now a very simple generation just for testing purposes
end
end
end



local mapHandl=mapHandler()
Chunks={}
Map={}
Buildings={}
Units={} -- first element in subarray(per unit): id, then epoch. Each epoch has separate ids but epoch makes it faster to find the unit. Then coords. Then health. Then, if needed, array for projectile coords
--they also have unit vector for where they are facing
--stats are located in epoch area to minimize memory impact
--each unit type like fror example archer or something has an id but then each unit has a subid independent of that that has the number in the array they are in
--when a unit dies that array space becomes open and then placed into another array that has all the free spaces and the free space just after the highest id unit
--when a projectiles locks on it locks onto an id and if its coords are the same as within some margin of the id unit's coords, it hits that unit
Epoch=0
GameStart=false
Resources={0,0,0,0,0,0,0}


function displayMap()
end

--bridge1 = image.copy(bridge2, 2.0 * image.width(bridge2), 2.0 * image.height(bridge2))
function fastSin(Radians)
end
function fastCos(Radians)
    --return fastSin(Radians-pi/2)
end
function on.construction()

timer.start(1/60)

end

function on.timer()

screen:invalidate()

end

function on.arrowUp()

sc = (var.recall("scale") or 0.5)

var.store("scale", sc + 0.1)

screen:invalidate()

end

function on.arrowDown()

screen:invalidate()
end

function on.paint(gc)
mapHandl:drawMap(gc)

end
  • Calculators owned: ti nspire, ti 84 plus se
My cemetech username is awesommee333.

Powered by EzPortal