1para{god-father} 105 Posted September 17, 2013 Is there any way to work out the size of a given town so I can place a marker over the whole town ? i.e AO_locations = nearestLocations [getPosATL player, ["NameCityCapital","NameLocal","NameCity","NameVillage","Strategic","CityCenter","NameMarine"], 25000]; _location = AO_locations call BIS_fnc_selectRandom; _pos = (position _location); //////create marker over town but how big ????//// Share this post Link to post Share on other sites
kylania 568 Posted September 17, 2013 It ends up looking silly, but yes. Unfortunately the code I'm thinking of is at home. All the marker sizes are short and stubby though. For each location you find you'll wanna look up: configfile >> "CfgWorlds" >> "Altis" >> "Names" >> "Anthrakia" >> "radiusA" and configfile >> "CfgWorlds" >> "Altis" >> "Names" >> "Anthrakia" >> "radiusB" for example. Share this post Link to post Share on other sites
David77 10 Posted September 17, 2013 Did they fix BIS_Fnc_SelectRandom? Share this post Link to post Share on other sites
kylania 568 Posted September 17, 2013 What's wrong with it? Share this post Link to post Share on other sites
David77 10 Posted September 17, 2013 Well back in Arma2 it worked, but there were better ways to get true random, as that function hardly ever would select the first & last elements. Tankbuster did alot of experimenting and posted some things about the function. It's been many many months ago though. Right after you took your last break from the forums =P ---------- Post added at 19:13 ---------- Previous post was at 19:09 ---------- here's the thread. Share this post Link to post Share on other sites
kylania 568 Posted September 17, 2013 (edited) It's a super simple function, if it's not working then there's a problem with random itself. :) private ["_ret","_i"]; if(count _this > 0) then { _i = floor random (count _this); //random index _ret = _this select _i; //get the element, return it _ret } else { ["Array has no elements."] call bis_fnc_error; nil }; Doing a quick test with [1,2,3] call BIS_fnc_selectRandom I got: 1, 3, 3, 1, 3, 3, 3, 1, 3, 3, 3, 2, 1, 2, 1, 2, 2, 1, 3, 2 from 20 runs. So 6x1, 5x2 and 9x3. Bet high! Edit: Looks like they rewrote it according to that ticket for ArmA3. :) Edited September 17, 2013 by kylania Share this post Link to post Share on other sites
David77 10 Posted September 17, 2013 (edited) Nice. That's a pretty even distribution. It used to be really bad especially for a small pool of elements. Edited September 17, 2013 by David77 Share this post Link to post Share on other sites
1para{god-father} 105 Posted September 17, 2013 It ends up looking silly, but yes. Unfortunately the code I'm thinking of is at home. All the marker sizes are short and stubby though. For each location you find you'll wanna look up:configfile >> "CfgWorlds" >> "Altis" >> "Names" >> "Anthrakia" >> "radiusA" and configfile >> "CfgWorlds" >> "Altis" >> "Names" >> "Anthrakia" >> "radiusB" for example. Many thanks So something like this would work do you think ? (unable to test atm ) _location = AO_locations call BIS_fnc_selectRandom; _locationName = (text _location); _radA=(configfile >> "CfgWorlds" >> "Altis" >> "Names" >> "_locationName" >> "radiusA") _radB=(configfile >> "CfgWorlds" >> "Altis" >> "Names" >> "_locationName" >> "radiusB") Share this post Link to post Share on other sites
kylania 568 Posted September 17, 2013 never mind, I found it! townLabels = false; // include name markers too { _town = text _x; _sizeX = getNumber (configFile >> "CfgWorlds" >> worldName >> "Names" >> (text _x) >> "radiusA"); _sizeY = getNumber (configFile >> "CfgWorlds" >> worldName >> "Names" >> (text _x) >> "radiusB"); _name = format["mrk_%1", _town]; _foo = createmarker [_name, getPos _x]; _foo setMarkerSize [_sizeX, _sizeY]; _foo setMarkerShape "ELLIPSE"; _foo setMarkerBrush "SOLID"; _foo setMarkerColor "ColorRed"; if (townLabels) then { _label = format["lbl_%1", _town]; _foo = createmarker [_label, getPos _x]; _foo setMarkerShape "ICON"; _foo setMarkerType "selector_selectedMission"; _foo setMarkerColor "ColorBlack"; _foo setMarkerText str((text _x)); _foo setMarkerAlpha 0.5; }; } forEach nearestLocations [getArray (configFile >> "CfgWorlds" >> worldName >> "centerPosition"), ["NameCityCapital","NameLocal","NameCity","NameVillage","Strategic","CityCenter","NameMarine"], 25000]; Share this post Link to post Share on other sites
mantls 2 Posted September 17, 2013 are locations working properöy now (they weren't properly configured in the Alpha/Beta afaik). i used this in the past weeks/months: m_fnc_getTownSize = { if (!isServer) exitWith {}; private ["_switch","_nearHouses1","_radius","_nearHouses2","_startPos","_nearHouses"]; _startPos = _this select 0; _radius = 20; _nearHouses = nearestObjects [_startPos, ["House"], _radius]; _switch = true; while {_switch} do { _nearHouses1 = nearestObjects [_startPos, ["House"], _radius]; _radius = _radius + 35; _nearHouses2 = nearestObjects [_startPos, ["House"], _radius]; if ((count _nearHouses1) == (count _nearHouses2)) then { _switch = false; }; }; _radius }; Share this post Link to post Share on other sites
kylania 568 Posted September 17, 2013 Altis and near-Anything commands is murder. The world config is already in memory. :) Share this post Link to post Share on other sites
mantls 2 Posted September 17, 2013 I noticed a bit of slow down, yeah. But if Locations are now properly configured i might aswell use them, ofc! :D Share this post Link to post Share on other sites
Larrow 2822 Posted September 17, 2013 Is it possible to do procedural colours or textures for marker colors or brushes? I was doing something similar to this the other week but going through the whole location types, doing a search on them then creating a marker over each one. (yes i was even doing mount lol). I end up with something like Kylania's script above but having to default to red as my marker color. Other than switching for a few types to change the marker color i tried several variation trying to change the color from the type config into a procedural color for the marker but with no luck. Anyone know if this is possible? Share this post Link to post Share on other sites
Tankbuster 1746 Posted October 24, 2015 So making a marker that sensibly shows the city limits using size (or the config extract method kylania used (they are the same thing)) doesn't produce nice results. For example, the x value of Oreokastro is 800! It should be 100 at most. Kavala is a big town, but it comes back as [500,200]. That's rubbish. Kavala is smaller than Neochori. *facepalm* And Kore extends north into the forest for way too far. Gravia size is massive! screenshot way too big for rules on image posting. hyperlink better http://images.akamai.steamusercontent.com/ugc/393296542110378404/32F6EB9B13AA2540C21055BCF44419ED8BEEC4DC/ There must be a better way of doing this. I'm thinking of trying to find the city limits by measuring house density at positions moving away from the centre of the town and when the density of housing at these positions goes below a certain threshold, then decide that must be the edge of the town. Share this post Link to post Share on other sites
fn_Quiksilver 1636 Posted October 25, 2015 So making a marker that sensibly shows the city limits using size (or the config extract method kylania used (they are the same thing)) doesn't produce nice results. For example, the x value of Oreokastro is 800! It should be 100 at most. Kavala is a big town, but it comes back as [500,200]. That's rubbish. Kavala is smaller than Neochori. *facepalm* And Kore extends north into the forest for way too far. Gravia size is massive! screenshot way too big for rules on image posting. hyperlink better http://images.akamai.steamusercontent.com/ugc/393296542110378404/32F6EB9B13AA2540C21055BCF44419ED8BEEC4DC/ There must be a better way of doing this. I'm thinking of trying to find the city limits by measuring house density at positions moving away from the centre of the town and when the density of housing at these positions goes below a certain threshold, then decide that must be the edge of the town. Since terrains dont change much, perhaps its more efficient to do it once by hand, just eyeball in the editor and put a marker down with a name which covers the area. Then preview and grab the data with copytoclipboard. Cant see it taking more than 30 minutes per terrain. Share this post Link to post Share on other sites
Tankbuster 1746 Posted October 25, 2015 Mission makers do usually do it by hand. I was hoping to avoid doing that because I want the mission to be more easily map portable and to avoid doing map specific data entry when moving the mission to other maps. Share this post Link to post Share on other sites
rübe 127 Posted October 25, 2015 Kavala is smaller than Neochori. *facepalm* [...] You can immediately tell this is all wrong by looking at Kylania's small screenshot. :o Once upon a time, at least official maps had proper configs... :( Anyways, here's a suggestion for some quickfix: Take the max (or min) of RadiusA and RadiusB, s.t. you end up with circles. Weight/scale the radius by a factor depending on the location type (CityCapital, City, Village, Local, ...). Or, alternatively scratch the original RadiusA/B alltogether, and just fix radius depending on location type. ...assuming at least the positions of the locations are fine. Share this post Link to post Share on other sites
Tankbuster 1746 Posted October 25, 2015 Ruebe, I've already done what you suggest in 1. I took the min of the two and yes, it gave circles with the crazy data like Oreokastro removed but it's stil very poor. Radius depending on type is the best we have so far, unless I can work out something that works out house density as it moves away from the centre of town. Yes, the positions of the locations are fine. Kavala could be a little further east, by perhaps 100m, but other that that, they are useable. Share this post Link to post Share on other sites
Tankbuster 1746 Posted October 26, 2015 OK, I've got a nice script based solution that's working well. I'll tidy it up and will have something to show in the next couple of days Share this post Link to post Share on other sites
zapat 56 Posted October 26, 2015 Here is what I use. It's a pretty complex thing, but I guess it goes as far as we'll ever need it to. It needs a lot of CPU time, so I suggest you to use it to create the array, which you save per map. Maybe an overkill for the OP's task, but you can use it for a lot more things. :) It is designed with A3 buildings in mind (the blacklist string parts are for omitting A3 stuff, which is not part of an A3 town: like power lines, walls, etc. - not omitting these you'll have pretty big cities. :) ) these can be altered in the settings (the first couple of lines) Run it by _cityArray = call BR_makeCityArray; The results are returned & reachable by BR_cityarray global variable & copied to clipboard as well. Array element format is (for each city)0: ARRAY of POSITIONS: points of the convex hull surrounding the city (not closed) 1: POSITION: city hull center position array2: SCALAR maximum perimeter of city hull (from center to farthest hull point) Oh, and thanks for the maths functions to grandmaster Rübe (from the good ol A2 times) BR_Grid_minSettlement = 10; //you need this many buildings to be treated as a city BR_Grid_settlementTreshold = 55; //gap between houses to be considered as in the same city BR_Grid_objBlacklistL1 = ["GHV"]; BR_Grid_objBlacklistL2 = ["CAL","GHV","WALL","BARR","POW"]; BR_Grid_houseObj = "House_F"; BR_Grid_ruinObj = "Ruins_F"; BRfn_convexHull = { /* Auhtor: r?be */ private ["_points", "_spacing", "_minDistance", "_n"]; _points = []; _spacing = 0; _minDistance = 0; if ((count _this) > 1) then { _spacing = _this select 1; }; if ((count _this) > 2) then { _minDistance = _this select 2; }; // empty list? if ((count (_this select 0)) == 0) exitWith { [] }; private ["_extrapolate"]; // 1 pos/object -> 4 positions _extrapolate = { private ["_pos", "_dir", "_x", "_y", "_offset"]; _pos = _this select 0; _dir = _this select 1; _x = ((_this select 2) select 0) * 0.5; _y = ((_this select 2) select 0) * 0.5; _offset = _this select 3; // apply offset if (((_offset select 0) != 0) || ((_offset select 1) != 0)) then { _pos = [_pos, _offset, _dir] call BRfn_offsetPosition; }; // return the four positions [ ([_pos, [(_x * -1), _y], _dir] call BRfn_gridOffsetPosition), ([_pos, [_x, _y], _dir] call BRfn_gridOffsetPosition), ([_pos, [_x, (_y * -1)], _dir] call BRfn_gridOffsetPosition), ([_pos, [(_x * -1), (_y * -1)], _dir] call BRfn_gridOffsetPosition) ] }; if ((typeName ((_this select 0) select 0)) == "OBJECT") then { // objects & bounding box private ["_box"]; { _box = _x call BRfn_boundingBoxSize; _points = _points + ([ (position _x), (direction _x), _box, (boundingCenter _x) ] call _extrapolate); } forEach (_this select 0); } else { // positions as points (no extrapolation needed) _points = +(_this select 0); }; _n = count _points; // no or too few points (to form a polygon)? if (_n < 3) exitWith { [] }; private ["_theta", "_convexity"]; // point1, point2 => scalar between 0 and 360 // returns NOT the angle made by p1 and p2 with // the horizontal but which has the same order // properties as the true angle (easier to compute) _theta = { private ["_theta", "_dx", "_dy"]; _theta = 0; _dx = ((_this select 1) select 0) - ((_this select 0) select 0); _dy = ((_this select 1) select 1) - ((_this select 0) select 1); if ((_dx == 0) && (_dy == 0)) then { _theta = 0; } else { _theta = _dy / ((abs _dx) + (abs _dy)); }; if (_dx < 0) then { _theta = 2 - _theta; } else { if (_dy < 0) then { _theta = 4 + _theta; }; }; (_theta * 90) }; // p0, p1, p2 => scalar, where // _convexity < 0 == clockwise // _convexity == 0 == collinear // _convexity > 0 == counter-clockwise _convexity = { ( (((_this select 1) select 0) - ((_this select 0) select 0)) * (((_this select 2) select 1) - ((_this select 0) select 1)) - (((_this select 1) select 1) - ((_this select 0) select 1)) * (((_this select 2) select 0) - ((_this select 0) select 0)) ) }; /****************************************************** * graham scan */ // 1) finds the extremal or lowest (and left-most) point // and puts/swaps it to the front for "_i" from 1 to ((count _points) - 1) do { if ((((_points select _i) select 1) < ((_points select 0) select 1)) || ((((_points select _i) select 1) == ((_points select 0) select 1)) && (((_points select _i) select 0) < ((_points select 0) select 0)))) then { [_points, 0, _i] call BRfn_arraySwap; }; }; // 2) sort by theta _points = [_points, { ([(_points select 0), _this] call _theta) }] call BRfn_arrayShellSort; // 3) graham scan private ["_convexHull", "_m"]; _convexHull = [ (_points select 0), (_points select 1) ]; _m = 2; for "_i" from 2 to _n do { // backtracking, eliminating right turns and // straight lines (collinear) while {(([ (_points select _m), (_points select (_m - 1)), (_points select _i) ] call _convexity) >= 0)} do { _convexHull call BIS_fnc_arrayPop; _m = _m - 1; }; //_convexHull = _convexHull + [(_points select _m)]; _convexHull set [(count _convexHull), (_points select _m)]; _m = _m + 1; [_points, _m, _i] call BRfn_arraySwap; }; // apply spacing if (_spacing != 0) then { private ["_center"]; _center = _convexHull call BRfn_polygonCentroid; [ _convexHull, { ([ _this, _spacing, ([_center, _this] call BIS_fnc_dirTo) ] call BIS_fnc_relPos) } ] call BRfn_arrayMap; }; private ["_relHullPos", "_reduceConvexHull"]; // relative access to points on the convex hull // [_index, _shift] => _index _relHullPos = { private ["_index", "_shift", "_n", "_pos"]; _index = _this select 0; _shift = _this select 1; _n = count _convexHull; _pos = _index + _shift; if (_pos < 0) exitWith { (_n + _pos) }; if (_pos >= _n) exitWith { (_pos % _n) }; _pos }; // recursively reducing the convex hull by // merging two points into a new one _reduceConvexHull = { private ["_n", "_i", "_j", "_d"]; _n = count _convexHull; if (_n < 5) exitWith { true }; for [{_i = 0}, {_i < _n}, {_i = _i + 1}] do { _j = [_i, 1] call _relHullPos; _d = (_convexHull select _i) distance (_convexHull select _j); if (_d < _minDistance) exitWith { private ["_newPoint", "_newHull", "_a1", "_a2", "_m", "_k"]; // merge point _i and _i + 1 into new point _newPoint = [ (_convexHull select ([_i, -1] call _relHullPos)), (_convexHull select _i), (_convexHull select _j), (_convexHull select ([_i, 2] call _relHullPos)) ] call BRfn_lineSegmentIntersection; // alter convex hull _newHull = []; _a1 = _i min _j; // will be overwritten with the new point _a2 = _i max _j; // will be dropped _m = 0; for [{_k = 0}, {_k < _n}, {_k = _k + 1}] do { if (_k != _a2) then { if (_k == _a1) then { _newHull set [_m, _newPoint]; } else { _newHull set [_m, (_convexHull select _k)]; }; _m = _m + 1; }; }; _convexHull = _newHull; // run again until the convex hull coudn't be // reduced any further... [] call _reduceConvexHull; }; }; true }; // apply minDistance if (_minDistance != 0) then { [] call _reduceConvexHull; }; // close hull/polygon _convexHull = _convexHull + [(_convexHull select 0)]; // return convex hull _convexHull }; BRfn_getFarthest = { /* function returns farthest element from a positions array to a given position Input: 0: [array of positions] or [array of objects] 1: position (2: max distance - optional) Return: [farthest element, index of farthest,distance] */ private ["_pos","_array","_dst","_idx","_elem","_ndst","_nArray"]; _array = _this select 0; _pos = _this select 1; _maxDst = 1000000; if (count _this > 2) then {_maxDst = _this select 2}; if (count _array == 0) exitWith {[]}; _elem = _array select 0; _dst = 0; _idx = -1; { _ndst = _pos distance _x; if (_ndst > _dst && _ndst < _maxDst) then { _dst = _ndst; _idx = _forEachIndex; _elem = _x; }; } foreach _array; [_elem, _idx,_dst] }; BRfn_inString = { /* Author: r?be Description: check's whether a given string is a substring of/in another string. Parameter(s): _this select 0: needle (string) _this select 1: haystack (string) Returns: boolean */ private ["_match", "_needle", "_haystack", "_n", "_h", "_index", "_i"]; _match = false; _needle = toArray (_this select 0); _haystack = toArray (_this select 1); _n = count _needle; _h = count _haystack; if (_n > _h) exitWith {false}; _index = 0; for "_i" from 0 to (_h - 1) do { if ((_haystack select _i) == (_needle select _index)) then {_index = _index + 1} else {_index = 0}; // needle found! if (_index == (_n - 1)) exitWith {_match = true}; }; // return result _match }; BR_Grid_GetSettlementHull = { private ["_startPos","_queue","_houses","_isFailed","_t","_failed","_pullIn","_b","_nearby","_no","_startObj","_chull","_center","_area","_perimax"]; _startPos = _this; _queue = []; _houses = []; //local fncs _isFailed = { private ["_failed","_t"]; _t = typeOf _this; _failed = false; if ((getPosASL _this) select 2 < 0) exitWith {true}; if (getNumber(configFile>>"CfgVehicles">>_t>>"scope") != 1) exitWith {true}; { if ( ([_x,toUpper _t] call BRfn_inString)) exitWith {_failed = true}; } foreach BR_Grid_objBlacklistL1; _failed }; _pullIn = { private ["_b","_nearby"]; _b = _this; if (_b getVariable ["BR_Grid_sid",0] != 1) then { _b setVariable ["BR_Grid_sid",1]; _houses set [count _houses,_b]; //[str (getPos _b),getPos _b,0,"hd_dot","ColorWhite"] call BRfn_marker; }; _nearby = (getPosATL _b) nearObjects [BR_Grid_houseObj, BR_Grid_settlementTreshold]; _nearby = _nearby + ((getPosATL _b) nearObjects [BR_Grid_ruinObj, BR_Grid_settlementTreshold]); { if (_x getVariable ["BR_Grid_sid",0] == 0) then { if (_x call _isFailed) then {_x setVariable ["BR_Grid_sid",-1]} else {_queue set [count _queue,_x]}; }; } foreach _nearby; }; //get a building _no = nearestObjects [[_startPos select 0, _startPos select 1, 0], [BR_Grid_houseObj,BR_Grid_ruinObj], BR_Grid_settlementTreshold]; //start searching loop if (count _no > 0) then { _startObj = _no select 0; _startObj call _pullIn; while {count _queue > 0} do { _startObj = _queue select (count _queue - 1); _queue resize (count _queue - 1); _startObj call _pullIn; }; }; //register _chull = []; if (count _houses > BR_Grid_minSettlement) then { _chull = [_houses,5] call BRfn_convexHull; //_center = _chull call BRfn_polygonCentroid; //_area = _chull call BRfn_polygonArea; //_perimax = ([_chull, _center] call BRfn_getFarthest) select 2; }; //unreg {_x setVariable ["BR_Grid_sid",nil];} foreach _houses; _chull }; BRfn_polygonCentroid = { /* Although Ruebe's algorithm below is far more precise, but it just doesn't work with small polys. Switched to a lot simplier method, which doesn't give you the actual centroid, but always gives you something like it. */ private ["_positions","_minx","_maxx","_miny","_maxy","_tx", "_ty"]; _positions = _this; _minx = 100000; _miny = 100000; _maxx = 0; _maxy = 0; { _tx = _x select 0; _ty = _x select 1; if (_tx > _maxx) then {_maxx = _tx}; if (_tx < _minx) then {_minx = _tx}; if (_ty > _maxy) then {_maxy = _ty}; if (_ty < _miny) then {_miny = _ty}; } foreach _positions; [_minx + ((_maxx - _minx) / 2),_miny + ((_maxy - _miny) / 2),0] }; //---------------------------------------------------------------------------------------------------- // // // //---------------------------------------------------------------------------------------------------- BR_makeCityArray = { _allCities = nearestLocations [getPos player, ["NameCityCapital","NameCity", "NameVillage"], 100000]; BR_cityarray = []; { startLoadingScreen[str _foreachIndex]; _set = (getPos _x) call BR_Grid_GetSettlementHull; _fset = []; if (count _set > 0) then { _center = _set call BRfn_polygonCentroid; _perimax = ([_set, _center] call BRfn_getFarthest) select 2; _fset = [_set,_center,_perimax]; }; BR_cityarray set [count BR_cityarray, _fset]; endloadingscreen; } foreach _allCities; copyToClipboard str BR_cityarray; BR_cityarray }; 1 Share this post Link to post Share on other sites
Tankbuster 1746 Posted October 26, 2015 That's a beast, zapat! Mine is a couple of dozen lines. :) Share this post Link to post Share on other sites
zapat 56 Posted October 26, 2015 Yeah, it may be an overkill for a marker (it takes like 10 secs on a 3.8 i5), but I needed exact city limits (to check when the player enters a city) and a coder NEVER works manually. Share this post Link to post Share on other sites
Tankbuster 1746 Posted October 26, 2015 and a coder NEVER works manually. Absolutely! Here's what I have so far. ALL done in code. Starting at locationPosition,it's sensing a dropoff in housing density in a series of enlarging circles to have a guess at where the city ends. http://images.akamai.steamusercontent.com/ugc/393296542118611259/AD8E400E811BD71DF78BE38CA9F249449E1EC29D/ http://images.akamai.steamusercontent.com/ugc/393296542118609922/09B1D23D7621ED8EE13008372EB1C6686095587C/ It's not perfect, there's some tweaking to be done. Some of the poor results are because the locationPosition is not well placed, Dionysios is worst. Because the six config browser is no more, it's hard to tweak my nearobjects statement to exclude silly stuff when counting houses. I know that house_f includes some stuff that is skewing my results, but without 6CB, it's a pain to optimise. Share this post Link to post Share on other sites
rübe 127 Posted October 27, 2015 Here is what I use. I think you can now get rid of BRfn_inString and simply use the find command instead (which can find (sub-)strings in strings since A3 or something...), which should speed things up a notch already. And maybe you could get rid of those _points = _points + [...]; constructs (or _a set [(count _a), ...] for that matter) and use pushBack instead (hm, a pushBackAll might be handy...), which again should be slightly faster, since the + operator constructs new arrays (though that got optimised at some point in A3 I think (see: Array: Adding (appending) to arrays)..., so not really sure what's happening nowadays.). As for the computation of the polygon centroid... I'm not sure why the algorithm would fail in some cases, but I doubt that it's about the size (as in area?) of the polygon. Maybe there is some bug that messes up the sequence/order of the points of a polygon, or...? Hm... Either way, sorry about that. :P Anyways; cool beans! B) Share this post Link to post Share on other sites
zapat 56 Posted October 28, 2015 Rübe: While I totally agree with you on using the new commands, I just thought that for a 0.1 sec speedup on a script running for 10 secs anyways (and only once per map): it just isn't worth the work... :) So I pasted them as is. Although it may impact the speed more, since all objects need to be checked against the string blacklist. And I don't remember the problem with the polygon centroid fnc either, but I must have had a good reason to go this way. Maybe if I had known you still around for A3 I'd have contacted you but now it all went down in my short memory toilet. Anyhow for the job most people need it for, current simplification works well. Share this post Link to post Share on other sites