Jump to content

[1.3] kOS Scriptable Autopilot System v1.1.3.0


erendrake

Recommended Posts

1 hour ago, Steven Mading said:

Not your fault.  You've discovered a bug.  I made an issue for it here, which you should have a look at to see the detailed description of the problem:

https://github.com/KSP-KOS/KOS/issues/1764

If you need an immediate workaround, you can swap the Y and Z axes of the vector you get back from SUN:VELOCITY:ORBIT (but only that one, not the other body's velocities), as shown in the screenshot accompanying the bug report link above.

If you do use that workaround, be aware that it's likely something we'll fix in a later release so be on the lookout for that in the release notes of future releases so you can un-swap them once it's fixed in the mod itself.

 

Great, thanks for the help! Will give it a try later.

Link to comment
Share on other sites

2 minutes ago, Joe32320 said:

Great, thanks for the help! Will give it a try later.

Just a note, what I wrote as "correct" is actualy not correct.  The vector is inverted in the wrong direction.  (in addition to not swapping the axes, it's *also* failing to invert the direction so it's the sun's velocity relative to kerbin instead of kerbin's velocity relative to the sun.)

I'm seeing if I can get the fix into the v1.0.0 official release coming up soon, rather than making you have to wait even longer for the next release after that.

 

Edited by Steven Mading
Link to comment
Share on other sites

Is there away in kOS V1.0.0-pre-1 to get the directory from the current running script? without setting the path in script! scriptpath() gives the whole path including the filename and extension and path() isn't updated until after the script has finished running.

 

Link to comment
Share on other sites

3 hours ago, Efratror said:

Is there away in kOS V1.0.0-pre-1 to get the directory from the current running script? without setting the path in script! scriptpath() gives the whole path including the filename and extension and path() isn't updated until after the script has finished running.

 

I'm confused by the description. What do you mean by "path() isn't updated until after the script has finished running" when combined with "without setting the path in script!".

If the script isn't setting the path, then what's this "updated" you're talking about mean?

 
Edited by Steven Mading
Link to comment
Share on other sites

Hello,

I had a request from @aeroz2011 to add support for kOS to my Historical Progression Tech Tree. I am looking for some help from those of you that use it as to the use of the various parts that come with the mod and where they should be located on a tech tree in the scope of a timeline. Please correspond the parts to years when they would have historically been used and I will fill in the rest.

  • KR-2042 b Scriptable Control System (5000)
  • CompoMax Radiual Tubeless (60,000)
  • CX-4181 Scriptable Control System (10,000)
  • KAL9000 Scriptable Control System (255,000)

For those of you that do not know, the Tech Tree starts out historically so there are Probes before manned craft.

Thanks for your help!

 

Link to comment
Share on other sites

6 hours ago, pap1723 said:

Hello,

I had a request from @aeroz2011 to add support for kOS to my Historical Progression Tech Tree. I am looking for some help from those of you that use it as to the use of the various parts that come with the mod and where they should be located on a tech tree in the scope of a timeline. Please correspond the parts to years when they would have historically been used and I will fill in the rest.

  • KR-2042 b Scriptable Control System (5000)
  • CompoMax Radiual Tubeless (60,000)
  • CX-4181 Scriptable Control System (10,000)
  • KAL9000 Scriptable Control System (255,000)

KAL9000 presumably isn't going to be around until a certain odyssey circa 2001...(okay, okay, first operational in 1992 technically...)

Link to comment
Share on other sites

How would one go about matching planes with a target during launch? I play RO, and launches usually require a constant burn to orbit. Inclination changes from already established orbits, even if very small, require massive amounts of dV, making a dogleg maneuver the best option. Any math you guys can throw at me to figure it out? 

Link to comment
Share on other sites

@dafidge9898

Let's think of the realistic way of finding launch to specific plane:

  1. First, note the conditions your launch profile puts you at the end of your main burn (it'll likely be suborbital with the circularization done later - take note of the suborbital trajectory): ground-relative velocity, distance from launch site and time since takeoff (yes, these may slightly vary for different inclinations, but this difference is kind of adding a few seconds after this).
  2. The ascent trajectory should stay approximately in the same surface-relative vertical (that is, perpendicular to the surface, that is, going through the center of the planet) plane. For any given launch azimuth, using the final velocity and covered distance in this plane, you can get the surface-relative position (right now you'll specifically need the latitude of it) and velocity (which then needs to be recalculated to orbital velocity, taking altitude and latitude into account) which will give you the inclination of the orbit. So you have to find the proper launch azimuth to get the necessary inclination (note: for inclinations above the launch site's latitude you get 2 solutions for 2 different launch windows every day). Also note that the optimal ascent profile doesn't have constant azimuth - it slightly changes with your latitude to keep in the same surface-relative plane).
  3. Now, we have to match not only the inclination, but also the longitude of ascending node. Note the surface-relative position of the ascent end point (for the found launch azimuth). Then find at witch point in time it will be in the proper orbital plane (so that orbit plane for this position and velocity has both inclination and LAN correct). Now subtract the time the ascent takes - and there is your launch time.

Now a bit of the practical navigation ideas: you have to find launch window and azimuth somehow like mentioned above (the slightly rough estimates are launching slightly before the launch site gets in orbit plane - the time difference here corresponds to the mostly vertical part of ascent, and having the launch azimuth as the azimuth of the surface velocity of something in the parking orbit when passing over the launch site's latitude), but you don't have to actually keep to the initial solution all the way to the orbit. Once you are out of the densest layers of the atmosphere (somewhat halfway through the ascent), you are getting to navigate in the orbital frame more efficiently.

Here, the actual values to use for the navigation are your current orbital-frame velocity, the orbital velocity in the closest point of your parking orbit (or your ascent-end orbital velocity projected into the target plane) and the distance from the desired orbital plane. And the goal is to get both the offset from the plane and the normal to the plane velocity component to zero slightly before your in-plane velocity reaches the final value (if you got the initial values close enough, it shouldn't differ too much from just an inclined launch without full orbit matching).

You should end in about the correct plane and then all you have to do is navigate the rendezvous in the plane.

Link to comment
Share on other sites

For those who are using kOS and Universal Storage, here is a cfg file to add a kOS processor for Universal Storage.

Here is the cfg file. Add it to the US_1R110_Wedge_KISContainer directory. Or if preferred, place it in a seperate directory but then you'll need to copy KASContainer.dds and model.mu from the US_1R110_Wedge_KISContainer directory to the new directory.

Link to comment
Share on other sites

Hi all, 

when has TIME:CLOCK changed from HH:MM:SS to HH:MM:SS,sssssssssssss? is there any possibility to round this to HH:MM:SS,ss ? (or just HH:MM:SS) ?

I've used one of my favorite mission screen monitor and I see that the time is complete messy :(

 

Link to comment
Share on other sites

@Alchemist Thank you so much! This will definitely assist. Just one thing:

On 8/6/2016 at 1:04 PM, Alchemist said:

First, note the conditions your launch profile puts you at the end of your main burn (it'll likely be suborbital with the circularization done later

My main burn is basically the entire burn to a circular 250x250 km orbit. I currently have a script that has reliably gotten me in a 250.5 x 250.3 km orbit with one almost continuous 14 minute burn (the only break in burning being the 2 seconds for stage separation). Does this make a big difference? Or are you referring to just the first stage burn?

Edited by dafidge9898
Typo
Link to comment
Share on other sites

2 hours ago, dafidge9898 said:

I currently have a script that has reliably gotten me in a 250.5 x 250.3 km orbit with one almost continuous 14 minute burn

The choice of such reference point is actually rather arbitrary. You may even go up to the point when you start circularization subroutine. Or take something about 70-80% through the launch. And 14 minutes (it's RSS scale, right?) sounds like a top stage with quite low TWR (for comparison, launch of Soyuz is about 9 minutes until spacecraft separation in slightly unstable orbit; then couple small correction burns to raise it). Depending on how much delta v that gives, you may want to take the reference point at this separation or somewhere in the middle of this top stage burn.

The thing is - this point actually matters mostly for launch window calculation - like at which point you are aiming to reach the actual plane of the orbit, leaving the rest for the in-plane acceleration. With the low-TWR top stage, if it gives you significant portion of delta v, It may be wise to take the reference point for the initial launch profile calculation somewhere halfway through it, while on the actual launch switch from just azimuthal launch to smarter plane alignment (if initial estimate is good enough, this shouldn't change the yaw much) at the start of this top stage, aiming for fully matching the planes about at the start of the final circularization.

Link to comment
Share on other sites

I’ve poked around the documentation, but haven’t seen this addressed: what are the conditions under which KSP will refuse to add a maneuver node?  It seems to be dV-bound, but I’m wondering if there’s more to it than that.

For context: I’ve been experimenting with a circularization script that uses the eccentricity value as an engine throttle, and I’ve gotten it to the point that I can get within 5 meters’ difference between Ap and Pe on the first burn (at ~79km altitude).  If I run the script again on the same craft, I can get within a 1m Ap/Pe difference (same alt.).  So I ran it a third time on the craft, just to see if I could get the Ap/Pe difference down to a few centimeters, because why not be ridiculous about it?  The program worked as expected, except no node was added.  I checked, and the calculated dV for the node was 0.002m/s.

So I can see why KSP would just refuse to participate in such insanity, but I was wondering if anyone here knew its outer limits for such things.  And also if there’s a way to get KSP to recognize such a problem, or show a returned error code, or some such, if KSP does in fact return an error.  Basically, a good way to tell in-script if an attempt to create a maneuver node will fail/has failed, so the script can exit gracefully or go to fallback code or something.

Edited by meyerweb
Typo fix
Link to comment
Share on other sites

I'm sorry if this is the wrong place to ask this question or if a similar question has been asked before.

I'm currently trying to create a CFCS for my aircraft that will eventually include regulation control surface movement in relation to aerodynamic factors and pilot input. At the moment though, I'm just trying to write a simple stability script until I have a more thorough understanding of k-OS.

From what I've observed, stock SAS relies on raw input to orient the craft, which for my already unstable planes results in quite a bit of wobble and eventually pitching down.  The purpose of this script is to instead use control surface trim to adjust the craft orientation so that the plane will continue in the direction designated by the user, with no deviation from that path.

I've already looked through the documentation on the wiki, but I'm still stuck as to where I should even begin, which is why I thought it would be a good idea to ask here from people with more experience.

Any help would be greatly appreciated.

Link to comment
Share on other sites

5 hours ago, Johnny#5 said:

I've already looked through the documentation on the wiki, but I'm still stuck as to where I should even begin, which is why I thought it would be a good idea to ask here from people with more experience.

I think the first question is: have you read about or tried to implement a PID loop?  What you’re trying to do will very likely rely on a PID loop or two.

Link to comment
Share on other sites

11 hours ago, Johnny#5 said:

I've already looked through the documentation on the wiki, but I'm still stuck as to where I should even begin, which is why I thought it would be a good idea to ask here from people with more experience.

Any help would be greatly appreciated.

Probably most commonly used ones are hovering scripts and powered landing scripts that rally on PID library.
Those are also good basic scripts, because stuff learned trough making those can also be used for more advancing scripts.

There is lot of examples for those available. I have also created my own version, to start learning kOS.
Tried to comment everything properly, so others can catch up more quiclkly. You can find example for download along with craft files, link is in signature. Although, crafts were made for KSP 1.1.2. but scripts and most of crafts should properly work in KSP 1.1.3. With slight dislaimer, I no longer recall if I updated scripts to use build in PID library or older one. Even if older variant with extra PID library is in archive, it wokrs and it is still good example to start with.

Link to comment
Share on other sites

On 2016/8/10 at 9:49 PM, meyerweb said:

I think the fist question is: have you read about or tried to implement a PID loop?  What you’re trying to do will very likely rely on a PID loop or two.

I was having trouble deciding between a simple if/else or a PID loop, but I'll be sure to use a PID. Thanks for your help.

One of the main things I'm unsure of is how to set the trim to stabilize the plane according to rotation. I probably should have made that bit a little clearer in my original post.

23 hours ago, kcs123 said:

Probably most commonly used ones are hovering scripts and powered landing scripts that rally on PID library.
Those are also good basic scripts, because stuff learned trough making those can also be used for more advancing scripts.

There is lot of examples for those available. I have also created my own version, to start learning kOS.
Tried to comment everything properly, so others can catch up more quiclkly. You can find example for download along with craft files, link is in signature. Although, crafts were made for KSP 1.1.2. but scripts and most of crafts should properly work in KSP 1.1.3. With slight dislaimer, I no longer recall if I updated scripts to use build in PID library or older one. Even if older variant with extra PID library is in archive, it wokrs and it is still good example to start with.

Thanks for this. I'll be sure to give the scripts a look to get a better feel for what I'm doing.

I'm more than happy to work with the older PID loop. It might help me get a better understanding on how to implement PID loops in general.

Thank you both so much for your help.

 

Edited by Johnny#5
Link to comment
Share on other sites

11 minutes ago, EpicSpaceTroll139 said:

Is there by any chance an up to date page which lists all the commands that KOS recognizes? I've looked all over and all I can find is this out of date KOS Wiki page. http://kos.wikia.com/wiki/KerboScript

This mod is awesome btw :D

 

There's full documentation (not just a list of commands) here:

http://ksp-kos.github.io/KOS_DOC/index.html

We try to keep it up to date as much as possible.  Right now it doesn't contain the new things in the pre-release candidate but that's deliberate.  It won't contain those until the candidate becomes a full release.

 

Link to comment
Share on other sites

V1.0.0 release is now official! 

(It is no longer pre-release.)

Available on spacedock and github and curse in the usual places.

I'll see if I can get @erendrake to change this thread's head post.

About the name:

kOS has been around long enough that we figured it was long overdue
for us to stop calling it 0.something. Lots of people are using it,
and we're worried about backward compatibility enough that we're not
really treating it like a Beta anymore. This version contains mostly
a few things that we knew might break backward compatibility so we'd
been putting them off for a long time. A jump to 1.0 seems a good time
to add those changes.

Of course, it has lots of other changes for whatever else was being
worked on since the last release.

BREAKING CHANGES

  • As always, if you use the compiler feature to make KSM files, you should recompile the KSM files when using a new release of kOS or results will be unpredictable.
  • New Subdirectories ability has deprecated several filename commands such as delete, copy, and rename. They will still work, but will complain with a message every time you use them, as we may be removing them eventually. The new commands deletepath, copypath, and movepath described below are meant to replace them.
  • When using a RemoteTech antenna that requires directional aiming, in the past you could aim it at mission control with SETFIELD("target", "mission-control") and now you have to say SETFIELD("target", "Mission Control") instead, due to changes in RT's naming schemes.
  • Previously the Y and Z axes of SUN:VELOCITY:ORBIT were swapped. (#1764) This has been fixed so it is now the same as for any other body, however scripts might exist that had previously been swapping them back to compensate for this, and if there were they would now break since that swapping is no longer needed.

NEW FEATURES

  • Subdirectories: (http://ksp-kos.github.io/KOS_DOC/commands/files.html) You are now able to store subdirectories ("folders") in your volumes, both in the archive and in local volumes. To accomodate the new feature new versions of the file manipulation commands had to be made (please go over the documentation in the link given above). In the Archive, which is really your Ships/Script/ directory on your computer, these subdirectories are stored as actual directories in your computer filesystem. (For example, the file 0:/dir1/dir2/file.ks would be stored at Kerbal Space Program/Shipts/Script/dir1/dir2.file.ks on your real computer.) In local volumes, they are stored in the persistence.sfs savegame file like usual. (Pull Request discussion record: #1567)
  • Communication between scripts on different CPUs of the same vessel or between different vessels. (http://ksp-kos.github.io/KOS_DOC/commands/communication.html)
    • A new structure, the Message, contains some arbitrary piece of data you choose (a number, a string, a list collection, etc), and some header information kOS will add to it that describes where it came from, when it was sent, and so on. What you choose to do with these arbitrary chunks of data is up to you. kOS only lets you send them. You design your own protocol for what the data means.
    • If RemoteTech is installed, a connection is needed to send a message to another vessel (but not to a CPU on the same vessel). And, the message won't actually show up in the other vessel's queue until the required lightspeed delay.
    • To handle KSP's inability to have different vessels far away from each other both fully loaded and active, you do have to switch scenes back and forth between distant vessels if you want them to have a conversation back and forth. Messages that were meant to arrive on a vessel while it wasn't within active loading range will wait in the recever's vessel queue until you switch to it, so you don't have to hurry and switch "in time" to get the message.
  • Added anonymous functions : (http://ksp-kos.github.io/KOS_DOC/language/anonymous.html) By placing arbitrary braces containing the body of a function anywhere within the script that an expression is expected, the compiler builds the function code right there and then returns a delegate of it as the value of the expression.
  • New 3rd-party addon framework (https://github.com/KSP-KOS/KOS/tree/develop/src/kOS/AddOns/Addon%20Readme.md) allows authors of other KSP mods to add hooks into kOS so that kOS scripts can interface with their mods more directly, without kOS developers having to maintain that code themselves in the kOS repository. (Pull Request discussion record: #1667)
  • allow scripted vessel launches KUNIVERSE:GETCRAFT(), KUNIVERSE:LAUNCHCRAFT(), KUNIVERSE:CRAFTLIST(), and KUNIVERSE:LAUNCHCRAFTFROM() allow you to script the changing of scenes and loading of vessels into those scenes. While this breaks the 4th wall quite a bit (how would an autopilot choose to manufacture an instance of the plane?), it's meant to help with script testing and scripts that try to repeatedly run the same mission unattended. (http://ksp-kos.github.io/KOS_DOC/structures/misc/kuniverse.html)
  • eta to SOI change: Added SHIP:OBT:NEXTPATCHETA to get the time to the next orbit patch transition (SOI change). (http://ksp-kos.github.io/KOS_DOC/structures/orbits/orbit.html#attribute:ORBIT:NEXTPATCHETA)
  • get control-from: Added SHIP:CONTROLPART to return the Part of the vessel that is currently set as its "control from here" part. (http://ksp-kos.github.io/KOS_DOC/structures/vessels/vessel.html#attribute:VESSEL:CONTROLPART)
  • maneuver nodes as a list:( New ALLNODES bound variable that returns a list of all the currently planned manuever nodes (the nodes you could iterate through with NEXTNODE, but rendered into one list structure). (http://ksp-kos.github.io/KOS_DOC/bindings#allnodes)
  • Several new pseudo-action-groups (akin to "panels on", that aren't action groups as far as stock KSP is concerned, but kOS treats them like action groups) were added. (http://ksp-kos.github.io/KOS_DOC/commands/flight/systems#kos-pseudo-action-groups)
  • Ability to get/set the navball mode (surface, orbital, target) with the NAVMODE bound variable: i.e. SET NAVMODE TO "SURFACE"..
  • UniqueSet structure. (http://ksp-kos.github.io/KOS_DOC/structures/collections/uniqueset.html) A collection intended for when all you care about is whether a equivalent object exists or doesn't exist yet in the collection, and everything else (order, etc) doesn't matter.

BUG FIXES

  • In some cases (#1661) the program wouldn't stop immediately when you execute a kuniverse command that reloads a save or switches scenes. It would instead finish out the remainder of the IPU instructions in the current physics tick. After the fix, causing a scene change (or reload) automatically stops the program right there since anything it does after that would be moot as the game is about to remove everything it's talking about from memory.
  • If using "Start on archive", with Remote Tech, a misleading "power starved" error was thrown when you reboot a probe that's out of antenna range. (#1363)
  • unchar("a") was apparently broken for a few months and we hadn't noticed. The root cause was that its implementation had to be edited to comply with the change that enforced the VM to only use kOS Structure types on the stack. The need for that change had been missed. (#1692)
  • Previously Infernal Robotics allowed you to move servos that weren't even on your own vessel and you shouldn't have direct control over. This has been fixed. (#1540)
  • Refactored previous non-working technique for quicksave/quickload to turn it into something that works. (#1372)
  • There were cases where using CTRL-C to abort a program would cause some old cruft to still be leftover in the VM's stack. This made the system fail to clear out the names of functions that were no longer loaded in memory, making it act like they were still reachable and call-able. (#1610)
  • Some types of Resource didn't contain the :DENSITY suffix like the documentation claimed they would. (#1623)
Link to comment
Share on other sites

Slooowly getting back into kOS and 1, I have forgotten most, and 2, there are some changes so my old scripts won't work. That's not an issue as I plan on fixing that BUT:

I am attempting to code a very simple throttle control for launch and the first 20-30 km. The idea is to use throttle (locked variable), weight (locking the variable to mass * 9.81 which apparently works) and current thrust which for the life of me I can't get. Reading the documentation but with all the changes this is going to take a while so:

Can someone please give me the snippet of code to gain access to current thrust to pass on to the variable?

 

EDIT: I came up with this

list engines in eng.

set twr_set to 1.5.
set g to 9.81.
set thr to 1.

lock weight to ship:mass*9.81.
lock thrust to eng[1]:thrust.
lock twr to thrust/weight.
lock throttle to thr.

until altitude > 30000
{
	if twr < twr_set and twr < 1
	{
		set thr to thr+0.001.
	}
	if twr > twr_set and twr > 0
	{
		set thr to thr-0.001.
	}
}

The problem here is the hardcoding of using engine 1 (a 2 stage rocket with engine 1 for the lifter stage and engine 0 for the ascending/injection stage). Is there a way to have the code look at whatever is the current engine, in a clever way that doesn't involve too much extra code as I intend to extend the code and need to keep it somewhat short.

Edited by LN400
Link to comment
Share on other sites

11 hours ago, LN400 said:

The problem here is the hardcoding of using engine 1 (a 2 stage rocket with engine 1 for the lifter stage and engine 0 for the ascending/injection stage). Is there a way to have the code look at whatever is the current engine, in a clever way that doesn't involve too much extra code as I intend to extend the code and need to keep it somewhat short.

You could iterate over all the engines and look for which of them are currently ignited, with this suffix:

http://ksp-kos.github.io/KOS_DOC/structures/vessels/engine.html?#attribute:ENGINE:IGNITION

list engines in all_engines.
local active_engines is LIST().
for e in all_engines {
  if e:ignition {
    active_engines.add(e).
  }
}
// Now active_engines is a list consisting of only those engines that are currently lit.

 

Link to comment
Share on other sites

Sorry if I'm necro-posting, but I'm working on a rover navigation / SLAM library. Does my code make sense?

BTW, lib_circle_nav is from KSLib.

@lazyglobal off.
import("lib_circle_nav").

// lib_a_star.ks finds the shortest path from a start node to a target node.
// Due to the nature of the algorithm, it will also avoid hills and cliffs.

// Define what a node is first.
function worldNode{
	parameter gridX, gridY. // Example: worldNode(5, 10) gives me a point 5 grid marks to the right of the ship, and 10 marks forward.
	local d is 2 * planet:radius.
	local c is constant:pi * d.
	local offset is (50 * 360) / c. // The nodes are 50 meters apart.
	local nodeX is offset * gridX.
	local nodeY is offset * gridY.
	local there is circle_destination(circle_destination(offset, 90, body:radius), offset, 0, body:radius).
	return lex("X", nodeX, "Y", nodeY, "alt", there:terrainheight, "gCost", gCost(), "hCost", hCost(), "fCost", (gCost() + hCost())).
}

// Then create the grid.
function grid
	local _height is 10.
	local _width is 19.
	local spacing is 50.
	local startPoint is ship:geoposition. // Center the grid on the ship
	local _grid is list().
	for {local i is (-_height / 2).}{until i >= (_height / 2)}do{
		for{local j is (-_width / 2).}{until j >= (_width / 2)}do{
			_grid:add(worldNode(i, j)).
		}
	}
	return _grid.
}

function endNode{
	parameter grid.
	for n in grid{
		// Pick the node on the far side of the grid with the lowest line-of-sight slope to the current node.
		
	}
}
function gCost{
	parameter start is worldNode(0, 0), thisNode.

	// 3D Pythagoras: Distance^2 = deltaX^2 + deltaY^2 + deltaZ^2.
	return sqrt((thisNode["X"] - start["X"])^2 +
				(thisNode["X"] - start["X"])^2 +
				(thisNode["alt"] - start["alt"])^2).
}

function hCost{
	parameter thisNode, end.
	
	// 3D Pythagoras: Distance^2 = deltaX^2 + deltaY^2 + deltaZ^2.
	return sqrt((thisNode["X"] - end["X"])^2 +
				(thisNode["X"] - end["X"])^2 +
				(thisNode["alt"] - end["alt"])^2).
}

function fCost{
	parameter start is worldNode(0, 0), thisNode, endNode.
	return gCost(thisNode) + hCost(thisNode, end).
}

// The actual pathfinding algorithm
function pathfinding{
	parameter endNode,
		grid is localGrid()
		startNode is worldNode(0, 0).
	
	local openList is list().
	local closedList is list().
	local path is list().
	return path.
}

function driveTo{
	parameter point.
}

 

Link to comment
Share on other sites

Guest
This topic is now closed to further replies.
×
×
  • Create New...