Random Textured Rolling Hills

Previous topic - Next topic

Slydog

This will generate random, endless rolling hills.
It scrolls into/out of view at a specified speed.

Points that scroll off the screen (to the left) are removed from the array.
When a new hill is needed, it creates one (toggling the direction up or down).
It uses this new hill (really just a height value) and slices up the hill horizontally into segments.
It will scroll this new hill into view, then repeat endlessly.
In memory, the array should only have enough segment slices to fill the screen, plus a bit before, and the next hill yet to be shown.

Controls:
[<-] - Cursor Left - Decrease Speed (by 0.05)
[->] - Cursor Right - Increase Speed (by 0.05)
[1] - Change display mode to 'MODE_TEXTURE'
[2] - Change display mode to 'MODE_TEXTURE_WIRE'
[3] - Change display mode to 'MODE_WIRE'
[4] - Change display mode to 'MODE_POLYGON'
[5] - Change display mode to 'MODE_SOLID'
(The display modes are explained in the source code, nothing much really, just for debugging)

I'm not a graphics guy, so I just whipped up a sample texture to demo this.
If you notice the light brown bottom dirt border, and if you switch to 'MODE_TEXTURE_WIRE', the middle line follows this dirt border.
(This is configurable in code by a percentage - it is set to 25%)

While this may not affect the display too much, you can use the mid-line coordinates to wire up a physics engine and use that line for the colliders.
(It does affect the display a bit, kinda confusing, check out the code where ever I used that variable).

I tried commenting the code, but I ran out of time.  May update it later.
I attached the png texture I used.
And I zipped up the project too.
I hope it works, I'm in a rush!

Notes:
- a 'hill' is just the x/y coordinates of a peak or valley
- segments are how I slice up the hill and use a COSINE function to provide the rolling effect
- when the texture runs out, I just start it over again, but it may not have fully displayed the full texture, so when it joins with the next segment, it may not join up nicely - not sure how to get around this
- play with the hill settings, but if it is too much of a slope, the polygons will overlap, I could provide a work around, but it is simpler to just live with this constraint

RollingHills.gbas
Code (glbasic) Select
GLOBAL sx%, sy%
GLOBAL speed# = 4.0
GLOBAL draw_mode% = MODE_TEXTURE
GLOBAL sp_texture%

GLOBAL hills AS THillPoints
GLOBAL hills2 AS THillPoints
Main()
END

FUNCTION Main:
SYSTEMPOINTER TRUE
GETSCREENSIZE sx, sy
SEEDRND MID$(PLATFORMINFO$("time"), 17, 2)
LIMITFPS 60
IF NOT SETCURRENTDIR("Media")
DEBUG "Media Folder Not Found . . . Exiting!\n"
END
ENDIF


// Initialize 'hill' TYPE
sp_texture = GENSPRITE()
LOADSPRITE "grass.png", sp_texture // Load texture
hills.sp_texture = sp_texture
hills.x_width = 15 // How wide each section strip is, the wider, the more 'chunky' looking hills
GETSPRITESIZE sp_texture, hills.texture_width, hills.texture_height // Sprite size of the ground texture
hills.texture_ht_up = hills.texture_height * 0.75 // How tall to make the top portion
hills.texture_ht_dn = hills.texture_height * 0.25 // How tall to make the bottom portion (together should add to 1.0)
hills.x_change_min = 180 // When creating a new hill peak, it must be at LEAST this distance from previous
hills.x_change_max = 220 // When creating a new hill peak, it must be at MOST this distance from previous
hills.y_change_min = 50 // When creating a new hill peak, the peak must be at LEAST this much higher/lower
hills.y_change_max = 190 // When creating a new hill peak, the peak must be at MOST this much higher/lower
hills.y_min = sy * 0.40 // Keep hills below top 20% of screen
hills.y_max = sy * 0.95 // Keep hills above bottom 5% of screen
hills.rgb_bottom = RGB(100,60,20)
GenerateHills(hills) // Initial seeding of hills to fill screen at start

// Initialize 'hill2' TYPE
sp_texture = GENSPRITE()
LOADSPRITE "snow.png", sp_texture // Load texture
hills2.sp_texture = sp_texture
hills2.x_width = 25 // How wide each section strip is, the wider, the more 'chunky' looking hills
GETSPRITESIZE sp_texture, hills2.texture_width, hills2.texture_height // Sprite size of the ground texture
hills2.texture_ht_up = hills2.texture_height * 0.25 // How tall to make the top portion
hills2.texture_ht_dn = hills2.texture_height * 0.75 // How tall to make the bottom portion (together should add to 1.0)
hills2.x_change_min = 220 // When creating a new hill peak, it must be at LEAST this distance from previous
hills2.x_change_max = 280 // When creating a new hill peak, it must be at MOST this distance from previous
hills2.y_change_min = 70 // When creating a new hill peak, the peak must be at LEAST this much higher/lower
hills2.y_change_max = 220 // When creating a new hill peak, the peak must be at MOST this much higher/lower
hills2.y_min = sy * 0.15 // Keep hills below top 20% of screen
hills2.y_max = sy * 0.60 // Keep hills above bottom 5% of screen
hills2.rgb_bottom = RGB(30,45,60)
GenerateHills(hills2) // Initial seeding of hills to fill screen at start

// Main Loop
WHILE TRUE
// Speed: "<-" and "->" cursor to decrease / increase
IF KEY(203)
DEC speed, 0.05
IF speed < 0.1 THEN speed = 0.1
ELSEIF KEY(205)
INC speed, 0.05
IF speed > 20 THEN speed = 20
ENDIF

// Draw Mode: Good for see what is happening behind the scenes
LOCAL key_hit$
key_hit$ = INKEY$()
SELECT key_hit$
CASE "1"
draw_mode = MODE_TEXTURE
CASE "2"
draw_mode = MODE_TEXTURE_WIRE
CASE "3"
draw_mode = MODE_WIRE
CASE "4"
draw_mode = MODE_POLYGON
CASE "5"
draw_mode = MODE_SOLID
ENDSELECT

CLEARSCREEN RGB(0,100,220) // Blue background - azure
ALPHAMODE -1.0 // In case your texture has alpha values
SMOOTHSHADING FALSE // Don't pixelate the graphics
hills2.Draw(draw_mode) // Draw current hills using current draw mode
hills2.Scroll(speed*0.3) // Scroll the hills left based on speed - if needed, a new hill will be generated
hills.Draw(draw_mode) // Draw current hills using current draw mode
hills.Scroll(speed) // Scroll the hills left based on speed - if needed, a new hill will be generated
SHOWSCREEN
WEND
ENDFUNCTION

FUNCTION GenerateHills: hill AS THillPoints
hill.Clear()

WHILE hill.hill_next.x < (sx + hill.x_change_max)
hill.GenerateNewHill()
WEND
ENDFUNCTION


types.gbas
Code (glbasic) Select
CONSTANT MODE_TEXTURE% = 1 // Normal textured display
CONSTANT MODE_TEXTURE_WIRE% = 2 // Wire outline overlayed on textured display
CONSTANT MODE_WIRE% = 3 // Wire outine of all calculated vectors
CONSTANT MODE_SOLID% = 4 // Fills in quads will solid colour - kinda neat when fast!
CONSTANT MODE_POLYGON% = 5 // Same as 'SOLID', except shades each polygon to see each polygon

CONSTANT PI = 3.14159

// General vector routine, makes other code cleaner
TYPE TVector
x#
y#

FUNCTION Set: x#, y#
self.x = x
self.y = y
ENDFUNCTION

FUNCTION Copy: v AS TVector
self.x = v.x
self.y = v.y
ENDFUNCTION

FUNCTION Scale: amount#
self.x = self.x * amount
self.y = self.y * amount
ENDFUNCTION

FUNCTION AverageWith AS TVector: v AS TVector
LOCAL rv AS TVector
rv.x = (self.x + v.x) / 2.0
rv.y = (self.y + v.y) / 2.0
RETURN rv
ENDFUNCTION

// Calc the magnitude / length of a vector
FUNCTION Magnitude#:
   RETURN SQR((self.x * self.x) + (self.y * self.y))
ENDFUNCTION
ENDTYPE

// A hill point is a point along the curved hill, a new one is generated every 'self.x_width' pixels
TYPE THillPoint
pu AS TVector // Higher point - used for top portion of texture (grass)
pm AS TVector // Base/mid point - good to use for physics engine as the 'contact' location
pd AS TVector // Lower point - used for the bottom portion of texture (dirt border)
texture_x# // How far along texture this point is
ENDTYPE

TYPE THillPoints
points[] AS THillPoint // All of the points along all of the hills
x_width% // How far between curve segments (the more, the smoother the curve)
texture_height# // How tall the ground texture is
texture_ht_up# // How tall the ground texture is - above the base point
texture_ht_dn# // How tall the ground texture is - below the base point
texture_width# // How wide the ground texture is
y_min% // How high up the screen a hill peak can reach
y_max% // How low on the screen a hill valley can reach
x_change_min% // New hill peaks/valleys must be at least this 'x' distance from previous
x_change_max% // New hill peaks/valleys must be at most this 'x' distance from previous
y_change_min% // New hills must be at least this far above or below
y_change_max% // New hills must be at most this far above or below
peak_valley_toggle% // Remember if previous hill was a peak or a valley, toggle each time
sp_texture% // Sprite ID of hill top texture
rgb_bottom% // Colour below the curve and texture (the bottom dirt colour)

hill_prev AS TVector // Used when calculating new hills
hill_next AS TVector // The current hill vector will be stored here

// Used to clear / initialize the data
FUNCTION Clear:
LOCAL y_adjust% = 30 // Used to make sure we don't start too high or too low
DIM self.points[0] // Clear previous points
self.hill_next.Set(0, RND(self.y_max - self.y_min - (y_adjust * 2)) + self.y_min + y_adjust) // Initialize 'hill_next' at a random valid 'y' pos
self.hill_prev.Set(0, 0) // Initialize 'hill_prev'
ENDFUNCTION

// Draw current hill points - various draw modes are available
FUNCTION Draw: mode% = MODE_TEXTURE
LOCAL hx%
LOCAL xy[] AS TVector
LOCAL uv[] AS TVector

SELECT mode

CASE MODE_WIRE
FOR hx = 1 TO LEN(self.points[]) - 1
DRAWLINE self.points[hx-1].pu.x, self.points[hx-1].pu.y, self.points[hx].pu.x, self.points[hx].pu.y, RGB(255,255,255)
DRAWLINE self.points[hx-1].pm.x, self.points[hx-1].pm.y, self.points[hx].pm.x, self.points[hx].pm.y, RGB(255,255,255)
DRAWLINE self.points[hx-1].pd.x, self.points[hx-1].pd.y, self.points[hx].pd.x, self.points[hx].pd.y, RGB(255,255,255)
DRAWLINE self.points[hx].pu.x,   self.points[hx].pu.y,   self.points[hx].pd.x, self.points[hx].pd.y, RGB(255,255,255)
NEXT

CASE MODE_POLYGON
STARTPOLY -1, 2
FOR hx = 1 TO LEN(self.points[]) - 1
PolyQuick(self.points[hx-1].pu, self.points[hx].pu, self.points[hx-1].pd, self.points[hx].pd, RGB(250,175,0), RGB(175,120,0))
NEXT
ENDPOLY

CASE MODE_SOLID
STARTPOLY -1, 2
FOR hx = 1 TO LEN(self.points[]) - 1
PolyQuick(self.points[hx-1].pu, self.points[hx].pu, self.points[hx-1].pd, self.points[hx].pd, RGB(250,180,0), RGB(250,180,0))
NEXT
ENDPOLY

CASE MODE_TEXTURE
DIM xy[2]
DIM uv[4]
xy[0].y = sy
xy[1].y = sy

// Bottom area first (dirt?)
STARTPOLY -1, 2
FOR hx = 1 TO LEN(self.points[]) - 1
IF self.points[hx].pm.x < sx + (self.x_width*2)
xy[0].x = self.points[hx-1].pd.x
xy[1].x = self.points[hx].pd.x
PolyQuick(self.points[hx-1].pm, self.points[hx].pm, xy[0], xy[1], self.rgb_bottom, self.rgb_bottom)
ENDIF
NEXT
ENDPOLY

// Now draw hills
uv[0].y = 0
uv[1].y = 0
uv[2].y = self.texture_height
uv[3].y = self.texture_height
STARTPOLY self.sp_texture, 2
FOR hx = 1 TO LEN(self.points[]) - 1
IF self.points[hx].pm.x < sx + (self.x_width*2)
uv[0].x = self.points[hx-1].texture_x
uv[1].x = self.points[hx].texture_x
IF uv[0].x = self.texture_width THEN uv[0].x = 0
IF uv[1].x < uv[0].x
uv[1].x = uv[0].x + self.x_width
ENDIF
uv[2].x = uv[0].x
uv[3].x = uv[1].x
PolyDraw(self.points[hx-1].pu, self.points[hx].pu, self.points[hx-1].pd, self.points[hx].pd, uv[0], uv[1], uv[2], uv[3])
ENDIF
NEXT
ENDPOLY

CASE MODE_TEXTURE_WIRE
self.Draw(MODE_TEXTURE) // First draw in texture mode
self.Draw(MODE_WIRE) // Then overlay with wire mode

ENDSELECT
ENDFUNCTION

FUNCTION Scroll: x#
FOREACH p IN self.points[]
DEC p.pu.x, x#
DEC p.pm.x, x#
DEC p.pd.x, x#
IF (p.pu.x < -(self.x_width*2)) AND (p.pd.x < -(self.x_width*2)) THEN DELETE p // Delete point if too far to the left offscreen
NEXT
DEC self.hill_prev.x, x#
DEC self.hill_next.x, x#

// Generate new hill yet?
IF self.hill_next.x < (sx + (self.x_width * 2)) THEN self.GenerateNewHill()
ENDFUNCTION

FUNCTION GenerateNewHill:
LOCAL x_dist%
ALIAS h0 AS self.hill_prev
ALIAS h1 AS self.hill_next

h0.Copy(h1) // Move old 'next' to 'prev'

// Random 'x' distance from previous peak
x_dist = self.x_change_min + RND(self.x_change_max - self.x_change_min)
x_dist = (x_dist / self.x_width) * self.x_width // Snap to 'hillPoints.x_width'
h1.x = h0.x + x_dist

// Random 'y' distance from previous peak
IF self.peak_valley_toggle = TRUE
// New point will be below previous (ie: a 'valley')
h1.y = h0.y + (self.y_change_min + RND(self.y_change_max - self.y_change_min))
ELSE
// New point will be above previous (ie: a 'peak')
h1.y = h0.y - (self.y_change_min + RND(self.y_change_max - self.y_change_min))
ENDIF

// Adjust 'y' to keep in range
IF h1.y < (self.y_min + self.texture_ht_up) THEN h1.y = (self.y_min + self.texture_ht_up)
IF h1.y > (self.y_max - self.texture_ht_dn) THEN h1.y = (self.y_max - self.texture_ht_dn)

// Toggle peak / valley bit for next entry
self.peak_valley_toggle = NOT self.peak_valley_toggle

self.FillCurve()
ENDFUNCTION

// Fill in curve between these two 'hillPeaks' entries
FUNCTION FillCurve:
LOCAL segments#, sx#
LOCAL dx#, da#, ymid#, ampl#
LOCAL hillPoint AS THillPoint
LOCAL v_delta AS TVector
LOCAL partial#, overage#
LOCAL length#
ALIAS h0 AS self.hill_prev
ALIAS h1 AS self.hill_next
segments = (h1.x - h0.x) / self.x_width

dx = (h1.x - h0.x) / segments
da =  PI / segments
ymid = (h0.y + h1.y) / 2.0
ampl = (h0.y - h1.y) / 2.0

FOR sx = 0 TO segments-1
hillPoint.pm.x = h0.x + sx*dx
hillPoint.pm.y = ymid + ampl * COS(sx*da * 180.0/PI)
IF LEN(self.points[]) = 0
hillPoint.texture_x = 0
ELSE
v_delta.Set(hillPoint.pm.x - self.points[-1].pm.x, hillPoint.pm.y - self.points[-1].pm.y)
length = v_delta.Magnitude()
hillPoint.texture_x = self.points[-1].texture_x + length
// Are we at the end of the texture, time to start at the beginning again
IF hillPoint.texture_x > self.texture_width
overage = hillPoint.texture_x - self.texture_width // How far over the end of the texture are we?
partial = overage / length // As a percentage (between 0 and 1)
// If it's really small, just stretch it to tne end of the texture
IF partial < 0.1
hillPoint.texture_x = self.texture_width
// If it's really large, snap the previous point to the end of the texture, and start this entry over
ELSEIF partial > 0.9
hillPoint.texture_x = length
self.points[-1].texture_x = self.texture_width
// Somewhere in between?  Then create a mid-point entry by splitting this polygon in two
ELSE
// Mid point entry
hillPoint.texture_x = self.texture_width
hillPoint.pm.x = h0.x + (sx-partial)*dx
hillPoint.pm.y = ymid + ampl * COS((sx-partial)*da * 180.0/PI)
self.AddPoint(hillPoint)
// Regular entry
hillPoint.texture_x = overage
hillPoint.pm.x = h0.x + sx*dx
hillPoint.pm.y = ymid + ampl * COS(sx*da * 180.0/PI)
ENDIF
ENDIF
ENDIF
self.AddPoint(hillPoint)
    NEXT
ENDFUNCTION

FUNCTION AddPoint: h AS THillPoint
DIMPUSH self.points[], h

IF LEN(self.points[]) = 1
self.points[0].pu.Set(0, self.points[0].pm.y - self.texture_ht_up)
self.points[0].pd.Set(0, self.points[0].pm.y + self.texture_ht_dn)
RETURN
ENDIF

ALIAS h0 AS self.points[-2]
ALIAS h1 AS self.points[-1]
LOCAL angle#
LOCAL v1 AS TVector
LOCAL v0 AS TVector

// Calculate 'p2' of HillPoint - form rectangle with previous point
angle = ATAN(h1.pm.y - h0.pm.y, h1.pm.x - h0.pm.x)  -90// Angle of the two points

v1.x = COS(angle) * self.texture_ht_up + h1.pm.x
v1.y = SIN(angle) * self.texture_ht_up + h1.pm.y
v0.x = COS(angle) * self.texture_ht_up + h0.pm.x
v0.y = SIN(angle) * self.texture_ht_up + h0.pm.y
h0.pu = h0.pu.AverageWith(v0)
h1.pu.Copy(v1)

v1.x = COS(angle) * -self.texture_ht_dn + h1.pm.x
v1.y = SIN(angle) * -self.texture_ht_dn + h1.pm.y
v0.x = COS(angle) * -self.texture_ht_dn + h0.pm.x
v0.y = SIN(angle) * -self.texture_ht_dn + h0.pm.y
h0.pd = h0.pd.AverageWith(v0)
h1.pd.Copy(v1)
ENDFUNCTION
ENDTYPE

FUNCTION PolyQuick: v1 AS TVector, v2 AS TVector, v3 AS TVector, v4 AS TVector, rgb1%, rgb2%
POLYNEWSTRIP
POLYVECTOR v1.x, v1.y, 0, 0, rgb1 // TL
POLYVECTOR v3.x, v3.y, 0, 1, rgb1 // BL
POLYVECTOR v2.x, v2.y, 1, 0, rgb2 // TR
POLYVECTOR v4.x, v4.y, 1, 1, rgb2 // BR
ENDFUNCTION


FUNCTION PolyDraw: v1 AS TVector, v2 AS TVector, v3 AS TVector, v4 AS TVector,  uv1 AS TVector, uv2 AS TVector, uv3 AS TVector, uv4 AS TVector
LOCAL rgb1% = RGB(255,255,255)
POLYNEWSTRIP
POLYVECTOR v1.x, v1.y, uv1.x, uv1.y, rgb1 // TL
POLYVECTOR v3.x, v3.y, uv3.x, uv3.y, rgb1 // BL
POLYVECTOR v2.x, v2.y, uv2.x, uv2.y, rgb1 // TR
POLYVECTOR v4.x, v4.y, uv4.x, uv4.y, rgb1 // BR
ENDFUNCTION


[EDIT] Fixed most / all bugs.  Updated demo for parallax.  See post below for details.

[attachment deleted by admin]
My current project (WIP) :: TwistedMaze <<  [Updated: 2015-11-25]

kaotiklabs

Vote Cthulhu! Because the stars are right!!!!
Ia Ia Cthulhu F' tang!

Schranz0r

I <3 DGArray's :D

PC:
AMD Ryzen 7 3800X 16@4.5GHz, 16GB Corsair Vengeance LPX DDR4-3200 RAM, ASUS Dual GeForce RTX™ 3060 OC Edition 12GB GDDR6, Windows 11 Pro 64Bit, MSi Tomahawk B350 Mainboard

Ian Price

I came. I saw. I played.

Kitty Hello


matchy


Slydog

#6
Thanks everyone!

QuoteCool. Add some Box2D!
I don't think that would be too hard, as the coordinates are ready to plug into Box2D.
I just never done any Box2D, plus I don't plan on using this code for anything specific yet.

It was more just to see if I could do it.  It took about 8 hours of programming.
I loved the challenge.

Dang, I could have used this for a base for a LD entry!
Throw in some Box2D, clouds, a 2nd or 3rd rolling hill (blurred / pastel) in the distance scrolling at different speeds, etc.
A Tiny Wings clone would be very easy.

Oh, if anybody is really considering that style game, here's a great blog I borrowed some ideas from:
How To Create A Game Like Tiny Wings (Part 1)

At least this all gave me a couple of ideas for future games though, if i ever finish my maze!

Anybody, feel free to use any / all of this to create a game.  That's why I did it!
My current project (WIP) :: TwistedMaze <<  [Updated: 2015-11-25]

matchy

It's pretty hard to make a game more popular than Happy Wheels.

erico

That is really great what you did there Slydog!
Quite hypnotic and the code itself is great (to the points I understood :-[).

Imagination plays a role while looking at the terrain and I swear I can almost see a guy on a monocycle riding around... :good:

Slydog

#9
I fixed most, if not all of the bugs.
See original post for the changes, and new screenshots!

I updated the demo to have a slow scrolling parallax snow-topped mountain.
Its speed is a factor of the faster normal hills.
This was fairly simple to add, after I fixed a few 'hard-coded' settings.

I think I also fixed the problem when the texture would repeat.
Before it would just cut off the overflowed texture and start at the beginning again.
Now I figure out exactly how far over the texture we are, and if it a small value (or large), I snap / stretch the texture to the edge, if the value is somewhere in between, I create a new hill point entry and split that polygon into two sections, each joining the end and the beginning of the texture, for a smooth continuous texture.  As long as your texture is tileable, you shouldn't see any join artifacts. 

That code got very confusing.  See the wire overlay mode to see what I'm talking about, every once in a while (when the texture ends) you will see one quad split into two to handle the transition.

Plus I sped up the scroll code by directly scrolling the x value, rather than calling a function to do that for every point.  To me it looks smoother, but still jerky once in a while.  If you plan on using any of this code, you may want to profile it and see where it is slowest.

I was just thinking this texture algorithm (take the hill code out) could be used to follow any dynamic path, such as a finger for drawing a textured line.  You would just have to smooth out the touch points, by eliminating lots and finding a curve algo between the main direction change points.  Or for drawing train tracks, etc.

[Edit] One final change, I updated to only draw visible polygons, should save some processing.  Before it was drawing all polygons, even the ones for the hills yet to be displayed.
My current project (WIP) :: TwistedMaze <<  [Updated: 2015-11-25]

Slydog

#10
Somebody (Aybe) on StackExchange was asking for advice on creating a laser beam effect that moves around following a spline based pattern.

I suggested this thread as a start, to help figure out the polygon coordinates and texturing, as the code above would work to follow any path.

Here's the question:
http://gamedev.stackexchange.com/questions/29424/what-are-the-maths-behind-raiden-2-purple-laser/29469#29469

But, he is having trouble viewing this page (or even the main GLBasic homepage).

QuoteGreat I will look at it. BTW, the link you gave is broken. – Aybe yesterday

The link doesn't appear to be broken for me... – Jonathan Hobbs yesterday

I just checked Random Textured Rolling Hills link right now, it brings me a 404 error. – Aybe 21 hours ago

Weird. It still works for me. But here's the underlying link: glbasic.com/forum/index.php?topic=8118 – Slydog 19 hours ago

Broken for me, I tried the home page, it shows 'It works !' and that's it ... weird. – Aybe 16 hours ago

Anybody know why?  I thought I read about the GLBasic site coming up as "It Works!" or something before, maybe on mobile devices or certain devices?

Gernot (or anybody), do you have a StackExchange account?  You could answer directly if you wish.
My current project (WIP) :: TwistedMaze <<  [Updated: 2015-11-25]

kanonet

If i remember right, IP6 only people have problems, especially some linux distros use IP6 only and no IP4. Maybe this is the problem?
Lenovo Thinkpad T430u: Intel i5-3317U, 8GB DDR3, NVidia GeForce 620M, Micron RealSSD C400 @Win7 x64

erico

Had some problems on it a few moons ago, it was my ISP blocking everything and it is fixed now.
Can he use a proxy or a dns to check?

mentalthink

Slydog you are a mosnter whit Glbasic!!!  :nw: :nw: :nw:

theprotocol

Well done!

This forum's been a source of inspiration for me lately.